# Fluent Pro Feature Matrix

## Why this exists

ChargeForms originally shipped with a site-activation-only license model:
"free, forever, no crippled features" — a license only raised how many
sites one key could run on, never gated a feature. That was an explicit
product decision, documented in `includes/class-license-client.php` and
`admin/class-license-page.php`.

That decision was explicitly reversed by direct instruction: match Fluent
Forms' real free/pro split — specific features locked behind a paid tier,
matching what an actual competitor does, not a novel model. This doc
records what Fluent actually gates (read from their real source, not
guessed) and which of those ChargeForms adopted, and why.

## What Fluent actually gates (from their real source)

Found by grepping `hasPro`/`has_pro` across the entire cloned
`fluentform/fluentform` admin source — not a partial list:

| Fluent feature | Where |
|---|---|
| Conditional logic on conversational forms | `FieldOptionsSettings.vue` |
| Post/Taxonomy field submission | `PostTypeSelectionModal.vue` |
| Premium form templates (`form.is_pro`) | `CreateNewFormModal.vue`, `ChooseTemplateModal.vue`, `predefinedFormsModal.vue` |
| Entries Import | `ImportEntriesModal.vue` |
| Advanced Default Style Template | `DefaultStyleTemplateSection.vue` |
| Pretty URL Base Slug (custom form URL) | `FormSettings/Layout.vue` |
| Integration-failure email notification | `FormSettings/Layout.vue` |
| Entry restrictions/limits | `FormSettings/Restrictions.vue` |
| Survey-result display customization (show label/count toggles) | `FormSettings/SurveyResult.vue` |
| ChatGPT-assisted form generation | `ChatGPTModal.vue` |

Pattern, not just a list: Fluent's Pro gate is almost entirely **advanced/
convenience/polish features layered on top of already-working free
functionality** — not core form-building, not payments, not the base
conditional-logic engine itself. A free-tier form still fully builds,
collects, and processes submissions.

## ChargeForms' adopted gates — status table

| Feature | Fluent Pro | ChargeForms equivalent | Exists? | Implemented? | Gated? | UI gate | Server gate | Verified |
|---|---|---|---|---|---|---|---|---|
| Import Entries (CSV) | Yes (`ImportEntriesModal.vue`) | `Entry_Importer`/`Import_Validator`, wired in `class-entries-page.php` | Yes | Yes | **Yes** | Badge on button + upsell modal (lock icon, message, Upgrade CTA) replacing the real upload form | `handle_import()` rejects with 403 when `!Settings::has_pro()` | Live Playwright: badge shown, upsell shown not real form, upgrade link correct, zero console errors (v0.109.0) |
| Premium form templates | Yes (`form.is_pro` in `CreateNewFormModal.vue`/`ChooseTemplateModal.vue`) | `New_Form_Page::templates()` (27 templates, 7 industry packs) | Yes (27 templates already existed) | Yes — 11 industry-pack-only templates marked `is_pro` | **Yes** | PRO badge next to title, action button becomes a direct Upgrade link (not a submit button) — a real UX improvement over Fluent's own version, which submits the form and only fails afterward with a plain toast and no upgrade link at all | `handle_create()` rejects with 403 when the requested template's `is_pro` is true and `!Settings::has_pro()` | Live Playwright: 11/27 cards locked, 11 badges shown, locked cards render an `<a>` not a submit button, **and** a direct `fetch()` POST to `admin-post.php` bypassing the UI entirely returns 403 (confirmed server-side enforcement, not cosmetic) |
| Default Style Template | Yes (`DefaultStyleTemplateSection.vue`) — real per-form custom CSS/JS/Form-Styler storage, "copy from an existing form," auto-applied to new forms | `default_style_template_enabled` — a single global on/off toggle that adds one predefined polish layer (card background/shadow/spacing) | **Not equivalent** — ChargeForms has no per-form custom CSS/JS storage or "copy styles from a form" mechanism at all | No | **Not gated** | — | — | N/A — see reasoning below |
| Pretty URL Base Slug | Yes (`FormSettings/Layout.vue`) — a real WP rewrite-rule-backed standalone form URL (`sitename.com/{slug}/my-form/`) | None | **No** — ChargeForms has zero rewrite-rule infrastructure; forms are shortcode-embedded only | No | **Not gated** | — | — | N/A — see reasoning below |
| Integration-failure email notification | Yes (`FormSettings/Layout.vue`) | `Integration_Manager::notify_on_failure()`, `integration_failure_email_enabled` — real, working, hooked to an actual `chargeforms_integration_error` action, sends a real email with the entry id and error | Yes, and it already fully works | Yes (pre-existing) | **Deliberately NOT gated** — explicit product decision, confirmed with the project owner | — | — | — |
| Entry restrictions (IP/Country only — Login Requirement, Empty Submission Blocking, and Keyword restriction are free even in Fluent) | Yes, but only 2 of ~5 restriction types (`Restrictions.vue`) | None at all | **No** — grepped the whole codebase for `restrict_form`/`ip_restriction`/`country_restriction`/`entry_limit`; zero matches | No | **Not gated** | — | — | N/A — see reasoning below |
| Save & Resume (front-end button field) | N/A — design boundary confirmed via `design_handoff_chargeforms/README.md`'s own "Deliberate decisions": "Save & Resume: engine free, button Pro. Tables, tokens and cleanup run for everyone; only the front-end button field is Pro." | `save_resume` field type (`builtins.js`, already marked `pro: true`); the engine itself (`Draft`/`Draft_Service`/`Draft_File_Stager`, `save_resume_enabled` setting) stays fully free and untouched | Yes — engine already existed and works for everyone | Yes — the client-side lock existed before this pass but was unconditional (always showed the upsell regardless of license, a leftover from before real licensing existed) | **Yes (this pass)** | `field.pro` check in `FieldBrowser.js` now respects `ChargeFormsConfig.hasPro` (newly exposed in `Menu::enqueue_builder_assets()`) — a licensed site inserts the field normally, an unlicensed site sees the existing lock badge + upsell modal | `RestApi::reject_locked_fields()`, called from both `create_form()` and `update_form()` — rejects with 403 only when the field type is *newly added* and no license; an **existing** form already using it (built while licensed) keeps working even if the license later lapses, via a grandfather check against the form's currently-saved fields | Live Playwright: `hasPro` correctly exposed as `false` with no license; palette shows the lock + upsell modal on click; **direct API POST bypassing the UI entirely** (creating a form with a raw `save_resume` field in the payload) correctly rejected with 403 and a clear message |

### Free/Pro template split — the actual reasoning

The 27 existing templates split cleanly along a line ChargeForms' own data
model already drew, not an arbitrary cut: 16 are general-purpose and
reachable from the main "Choose a Template" grid; 11 are explicitly
commented in the source as **"Industry pack-only templates ... only
reachable via a pack's own view"** (real_estate, healthcare, legal,
restaurant, events, nonprofit packs). The free tier keeps every
general-purpose template (contact forms, job applications, event
registration, donations, surveys, bookings, memberships, etc.) — a
genuinely complete, useful template library. Pro gates only the
niche, industry-specific ones bundled into the pre-existing "Packs"
concept, which was already presented as a curated/premium grouping
before this pass. This is deliberately narrower than gating "half the
templates" — it targets the templates whose absence doesn't make the
free product feel crippled, matching the product rule: free stays
genuinely useful, Pro unlocks *meaningful* rather than *arbitrary*
capability.

### Default Style Template — why not gated (real gap, not built)

Read `DefaultStyleTemplateSection.vue` in full: Fluent's version lets a
site owner pick an existing form, copy that form's custom CSS/JavaScript/
Form-Styler settings, and set the result as the default new forms inherit.
ChargeForms' `default_style_template_enabled` is a single boolean that
turns on one fixed, built-in polish layer — there's no per-form custom
CSS/JS storage anywhere in the codebase for it to copy *from*. Gating the
existing toggle and calling it "Default Style Template" would be
dishonest — it isn't the same feature, just a same-sounding name. The
real prerequisite (a per-form custom-CSS/JS settings tab with actual
storage) is a genuine feature to build first, not a lock to add now.

### Pretty URL Base Slug — why not gated (real gap, not built)

Read `FormSettings/Layout.vue`: Fluent's version needs a registered WP
rewrite rule and a template-redirect handler that renders a form standalone
at its own URL, not embedded in a page/post. Grepped the whole ChargeForms
codebase for `add_rewrite_rule` — zero matches. ChargeForms forms are
shortcode-embedded only; there is no foundation to gate here at all, let
alone fake one.

### Entry Restrictions (IP/Country) — why not gated (real gap, not built)

Read `Restrictions.vue`'s actual gate points: Fluent's free tier already
includes a master "Restrict Form Submission" toggle, Login Requirement,
Empty Submission Blocking, and Keyword-based restriction — only
**IP-based** and **Country-based** restriction are wrapped in
`showProRestrictionFields && hasFluentformPro`. ChargeForms has none of
this — no restrict-form toggle, no restriction storage, no entry-time
enforcement of any kind. Building an honest Pro gate here means building
Fluent's entire *free*-tier restriction system first (the toggle, Login
Requirement, Empty Submission Blocking, Keyword restriction — all
free even in Fluent), and only THEN gating the two specific types
(IP/Country) that are actually Pro there. Gating something ChargeForms
users have never had at all, with no free-tier restriction system
underneath it, wouldn't be "adding a Pro boundary" — it'd be building a
brand-new feature and immediately locking all of it, which contradicts
the product rule directly ("Pro should unlock meaningful advanced
capabilities" implies a free tier exists to advance *from*).

### Integration-failure email notification — why deliberately kept free

Confirmed via source read (`Integration_Manager::notify_on_failure()`)
that this is real, already-working functionality: hooked to an actual
`chargeforms_integration_error` action, sends a genuine email naming the
failed integration, the entry id, and the underlying error. Unlike CSV
import or niche templates, this is a **reliability signal** — the only
way a free-tier site owner would otherwise learn a webhook silently
started failing. Presented as an explicit product decision rather than
decided unilaterally (per this task's own instruction to stop at genuine
product-strategy calls): confirmed with the project owner to keep it
free rather than regress existing working functionality to match
Fluent's boundary. Matches the task's own product rule directly — "do
not add arbitrary restrictions simply because Fluent has them."

Implemented as the first, fully-verified proof of the new model — not a
placeholder. `Settings::has_pro()` (`includes/class-settings.php`) is the
one shared gate-check every future Pro feature must call, so the
definition of "has Pro" never drifts between call sites. License copy
(`admin/class-license-page.php`, `includes/class-license-client.php`,
`readme.txt`) updated to stop claiming "no crippled features," since that
claim is no longer true.

## Explicitly NOT gated this pass (deliberate, not an oversight)

Everything else on Fluent's list above. Each would need its own real
scoping pass — e.g. "Pretty URL Base Slug" doesn't have a ChargeForms
equivalent yet at all (would need to be built free-tier first before a
Pro-gate on it means anything); "premium templates" needs actual premium
template content to exist before gating make sense; "Advanced Default
Style Template" needs the underlying style-template feature itself
identified in ChargeForms first. Gating a feature that doesn't functionally
exist yet would be exactly the fake-lock/placeholder pattern this project's
engineering standards forbid — each addition to this list should follow
the same pattern as Import Entries: real working feature already exists
(or gets built) → gate added → server-side enforced → verified live →
committed, one at a time.

## What ChargeForms keeps free regardless (explicit, from the original task brief §9)

Per prior, explicit instruction protecting ChargeForms' existing
advantages — none of the following are candidates for gating, even though
Fluent locks some adjacent things:

- Nested AND/OR/NOT conditional logic, Rule Inspector, circular-reference
  detection (ChargeForms' conditional logic is already ahead of Fluent's
  free tier, which doesn't have nested groups at all)
- Copy/Paste, multi-select in the Builder

**Amendment reverted (explicit, direct instruction):** the payment
fields were briefly Pro-gated (see git history), which drifted out of
sync with `readme.txt`'s free-feature list — a real WordPress.org
submission problem, since the plugin advertised payments as free
everywhere while the code blocked inserting any payment field on an
unlicensed site. Reverted back to free on review: matching Fluent's
own real gates (which never touch payments — see the pattern above)
was reconfirmed as the actual intended model. All four payment fields
— Payment Item, Custom Amount, Payment Method (Stripe/PayPal), Payment
Summary — are free again; `assets/src/builder/fields/builtins.js` and
`Field_Type_Gate::PRO_FIELD_TYPES` no longer reference them.

## Implementation reference

- Gate check: `Settings::has_pro()` — `includes/class-settings.php`
- First applied gate: `admin/class-entries-page.php` (`render()`'s Import
  Entries button/modal, `handle_import()`'s server-side check)
- UI pattern for future gates: `.chargeforms-pro-badge-inline` (small
  inline "PRO" tag on an otherwise-visible action) + `.chargeforms-pro-upsell`
  (lock icon + message + Upgrade CTA, replacing the real UI) —
  `assets/css/admin.css`
