Skip to main content

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_payment until 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 new cf_can_view_form_analytics filter 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 to default). 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_limit setting plus cf_submit_rate_limit filter. Default unlimited.

Reliability

  • Migrations fast-path. _cf_maybe_run_migrations() returns immediately when the schema version matches the build. Migrations also run via upgrader_process_complete so plugin updates apply the schema without waiting for an admin page hit.
  • Scheduled cleanup. Daily crons purge stale drafts (cf_cleanup_drafts, TTL cf_draft_ttl_days, default 30) and old analytics rows (cf_cleanup_analytics, TTL cf_analytics_retention_days, default 365). Both unschedule on plugin deactivation.
  • Action queue self-heals. Rows stuck in processing past cf_action_queue_stale_minutes (default 10) are requeued.
  • Tracker idempotency. Pass meta['event_id'] to Tracker::record() (or event_id to the cf_track_event AJAX 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 and aria-hidden on the rest, with a polite aria-live region 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: reduce disables the fullscreen step transitions.
  • Logical-property RTL fixes for the select chevron and multi-step nav.

Modernisation

  • composer.json now requires PHP 8.1+.
  • Schema\Field and Schema\FormSchema properties are typed; both files declare strict_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/v1 namespace, AJAX action keys, and every filter/action documented in docs.md keep 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.