Upgrading to Core Forms 4.1
4.1 bundles a polish-and-hardening pass over the 4.0 pillars and adds payment-required forms backed by Stripe, PayPal, Razorpay, and Polar.sh. Existing forms keep rendering identically — payments are opt-in per form.
Minimum requirements
| 4.0.x | 4.1 | |
|---|---|---|
| PHP | 7.4 | 8.1 |
| WordPress | 6.0 | 6.4 |
Sites still on PHP 7.4–8.0 should stay on the 4.0.3 release. Auto-update
will be skipped automatically by WordPress when the host fails the
Requires PHP header check.
Payments
The big addition. See docs/payments/overview.md for the full per-provider walkthrough. In short:
- A new Payments section in Core Forms → Settings holds API credentials per provider, with a single test/live mode toggle that gates which keys get used.
- A new Payment tab on every form lets you enable payment, pick a
provider, set the amount (fixed or
[field_name]), currency, and a post-payment redirect URL. - Forms with payment enabled redirect submitters to the provider's
hosted checkout. The submission stays in
pending_paymentuntil the provider's webhook confirms the charge — only then do the existing form actions (email, Mailchimp, Slack, etc.) fire. - New REST endpoints under
/wp-json/cf/v1/payments/{provider}/webhook.
Schema migration
cf_submissions gains five columns and one composite index. Existing
rows get NULL in the new columns; nothing else is touched.
| Column | Type | Notes |
|---|---|---|
payment_status |
VARCHAR(30) | pending_payment / paid / payment_failed / refunded |
payment_provider |
VARCHAR(20) | stripe / paypal / razorpay / polar |
payment_id |
VARCHAR(190) | Provider-issued id |
payment_amount |
BIGINT UNSIGNED | Minor units (cents/paise) |
payment_currency |
VARCHAR(8) | ISO-4217 |
Index idx_payment_id (payment_provider, payment_id) for webhook
lookups.
Polish & hardening
Security
- Per-form analytics authorisation.
/wp-json/cf/v1/analytics/{form_id}/...resolves the form, verifies it exists, and runs the newcf_can_view_form_analyticsfilter so you can narrow access to a form's post author:
php
add_filter( 'cf_can_view_form_analytics', function ( $allowed, $form_id ) {
$post = get_post( $form_id );
return $post && (int) $post->post_author === get_current_user_id();
}, 10, 2 );
- Webhook signer key rotation. Outgoing workflow webhooks now
include
X-CF-Key-Id(defaults todefault).WebhookSigner::verify()accepts a callable as the secret param so the receiver can resolve the secret per key id. - Public submission rate limiter. Per-form
submit_rate_limitsetting pluscf_submit_rate_limitfilter. Default unlimited.
Reliability
- Migrations fast-path.
_cf_maybe_run_migrations()returns immediately when the schema version matches the build. Migrations also run viaupgrader_process_completeso plugin updates apply the schema without waiting for an admin page hit. - Scheduled cleanup. Daily crons purge stale drafts
(
cf_cleanup_drafts, TTLcf_draft_ttl_days, default 30) and old analytics rows (cf_cleanup_analytics, TTLcf_analytics_retention_days, default 365). Both unschedule on plugin deactivation. - Action queue self-heals. Rows stuck in
processingpastcf_action_queue_stale_minutes(default 10) are requeued. - Tracker idempotency. Pass
meta['event_id']toTracker::record()(orevent_idto thecf_track_eventAJAX endpoint) so retried client requests don't double-count funnel events. - Atomic CAS on payment dedup. Concurrent webhook deliveries can no longer double-fire the action loop — the paid-state transition uses a conditional UPDATE.
A11y / UX
- Fullscreen multi-step exposes
aria-current="step"on the active question andaria-hiddenon the rest, with a politearia-liveregion that announces step changes. - Inline validation no longer fires on first blur of an empty required field — errors appear after the user has interacted with the field or submitted the form.
prefers-reduced-motion: reducedisables the fullscreen step transitions.- Logical-property RTL fixes for the select chevron and multi-step nav.
Modernisation
composer.jsonnow requires PHP 8.1+.Schema\FieldandSchema\FormSchemaproperties are typed; both files declarestrict_types=1. Public API is unchanged.- CI matrix runs PHP 8.1 / 8.2 / 8.3.
New hooks
| Hook | Notes |
|---|---|
cf_can_view_form_analytics |
Gate analytics REST endpoints per form. |
cf_submit_rate_limit |
Per-form submit-rate-limit override. |
cf_draft_ttl_days |
Stale-draft cleanup TTL. |
cf_analytics_retention_days |
Analytics retention TTL. |
cf_action_queue_stale_minutes |
Workflow-queue stale-recovery threshold. |
cf_payments_orchestrator |
Returns the payments orchestrator. |
cf_payment_completed |
Fires once a webhook confirms a payment. |
Things that did not change
- Form HTML, schema JSON, post meta, and submission table layouts for free forms continue to render and store identically.
- Shortcode, block name, REST
cf/v1namespace, AJAX action keys, and every filter/action documented indocs.mdkeep working the same way.
If something breaks
The 4.0.3 tag is preserved on git. To roll back:
wp plugin install https://github.com/wpgaurav/core-forms/archive/refs/tags/v4.0.3.zip --force
Then file an issue at https://github.com/wpgaurav/core-forms/issues
with the 4.1-regression label.