Skip to main content

Core Forms 4.1

Payments on form submission with Stripe, PayPal, Razorpay, Polar.sh and FluentCart, a headless REST API for non-WordPress sites, six payment-ready templates and a rebuilt submissions screen. Released May 11, 2026. Requires PHP 8.1 and WordPress 6.4.

Core Forms 4.1 is out as of May 11, 2026. The change most people will notice is payments on form submission, with Stripe, PayPal, Razorpay, Polar.sh and FluentCart supported out of the box. The second one is a headless REST API, so a form built in WordPress can be rendered and submitted from an Astro or Next.js site, or any page that is not WordPress at all.

This post covers what you will see in the admin and on the front end, and the smaller fixes sit at the end. Existing forms render exactly as they did in 4.0.3, because payments are opt-in per form. The one thing to check before updating is the PHP version, since 4.1 needs PHP 8.1 and WordPress 6.4, and the next section explains what happens on a host that does not have them.

The update is waiting under Dashboard, then Updates. The upgrade guide has the full list of new hooks and the rollback command, and if something does not match this post, the support page is where to tell me.

Before You Update

The minimum requirements moved:

4.0.x4.1
PHP7.48.1
WordPress6.06.4

A site on PHP 7.4 to 8.0 should stay on 4.0.3. WordPress reads the Requires PHP header and skips the automatic update on its own when the host fails the check, so nothing breaks, but you will not see 4.1 until the host moves.

The submissions table gains 5 payment columns and one index during the update. Existing rows get NULL in the new columns and nothing else in the table is touched. Free forms, their HTML, schema JSON, post meta and shortcodes all keep working unchanged.

If something goes wrong, the 4.0.3 package is still available and the upgrade guide has the one-line rollback.

What’s New in Core Forms 4.1

Two large features and one admin pass make up the release. The features are independent, so a site that never takes a payment can still use the headless API, and the other way round.

Payments on Submission

A form can now require a payment before its actions run. You connect a provider once under Settings, then Payments, and turn on the Payment tab on any form that needs it. Five providers are in the box: Stripe, PayPal, Razorpay, Polar.sh and FluentCart. Each one talks to the provider’s own REST API. No third-party SDK is bundled.

The important part is what happens between submit and paid. The visitor is redirected to the provider’s hosted checkout and the submission is stored with the status pending_payment. Nothing else fires. When the provider’s signed webhook confirms the charge, the status becomes paid and only then does the action loop run: the email, the Slack message, the CRM contact. A failed or refunded payment gets its own status, payment_failed or refunded, so the inbox tells you what happened without opening the provider’s dashboard.

Signature verification is per provider: Stripe’s Stripe-Signature HMAC, PayPal’s verify-webhook-signature API call, Razorpay’s X-Razorpay-Signature HMAC and Polar.sh’s Standard Webhooks HMAC-SHA256. The paid-state transition uses an atomic compare-and-swap, so when a provider retries a webhook the actions do not fire twice. Return URLs bind the outcome and expire after 2 hours, which closes the replay route.

Webhooks arrive at /wp-json/cf/v1/payments/{provider}/webhook. The amount can be fixed or read from a field with a [field_name] reference, and each form sets its own currency and post-payment redirect.

FluentCart Hosted Checkout

FluentCart works differently from the other four, and on purpose. Instead of charging inside Core Forms, the form turns the submission into a FluentCart cart and redirects to FluentCart’s own checkout. You pick a product variation on the Payment tab. Free and Pro are both supported, subscriptions included.

Address fields on the form prefill FluentCart’s checkout and feed its tax engine, so the visitor does not land on an empty checkout after filling your form. When FluentCart marks the order paid, the submission becomes paid through the fluent_cart/order_paid_done hook, and refunds round-trip the same way. The FluentCart gateway only registers when the FluentCart plugin is active, and its test/live badge reads FluentCart’s own order mode rather than the Core Forms global flag.

Headless REST API

A new module registers 3 routes under /wp-json/core-forms/v1:

  • GET schema, which returns the rendered HTML and a structured field list.
  • POST submit, which runs the same processing pipeline a normal submission goes through, spam checks included.
  • GET embed.js, a drop-in widget.

One API key per site lives under Settings, then Headless, with generate, show and copy controls. Rotating the key invalidates every existing client at once. Each form gets a Headless tab with three copy-paste snippets already filled with that form’s submit URL and the active key: a vanilla JavaScript widget, a native fetch call and a cURL recipe.

The tab also carries a guidance card on keeping the key private, with server-side proxy templates for Cloudflare Workers and Pages Functions, the Next.js App Router and Astro server endpoints, plus a checklist for the proxy: an origin allowlist, a body size cap and a user-agent sanity check. The card also says why a honeypot does not belong at the proxy, which is that browser autofill fills it and eats real submissions. CORS is reflected per origin and captcha is enforced upstream, so a cross-origin submission goes through the same spam pipeline as one from the site.

Six Payment-Ready Templates

Add New Form now offers 6 starting points with a provider preselected:

  • One-time payment (Stripe), fixed price with name, email and notes.
  • Donation (PayPal), with a donor-entered amount through an [AMOUNT] field reference.
  • INR payment (Razorpay), ₹999 with name, email and phone.
  • FluentCart product, the hosted checkout handoff with a billing address fieldset matching FluentCart’s checkout schema.
  • Subscription (FluentCart Pro), a recurring variation.
  • Event ticket (Stripe), where a tier select drives the [TIER] amount.

The template registry gained settings and messages callbacks beside markup. Every starting point, payment or not, now ships with a success message that fits it. The booking template says a confirmation email is on its way, the PayPal template says it is redirecting to PayPal, and the generic “We will be in touch soon” is gone. Payment templates also override the default error copy.

Admin Pass

The per-form Settings, Messages and Payment tabs are now native details/summary accordions, each with a state badge so the shape of a form is visible without opening every section: the rate-limit count, whether a schedule window is set, honeypot on or off, the confirmation type, custom CSS and JS indicators, and the FluentCart variation’s price, type and subscription summary.

The per-form Submissions list was rebuilt around Inbox, Spam and All tabs with live counts, so you no longer bounce to the global Spam page to triage one form. The table has status badges, row stripes for new and spam rows, per-row actions, relative dates with the absolute date on hover, a select-all checkbox, bottom pagination and different empty states per view.

The All Submissions page was rebuilt for WordPress 7.0. The toolbar splits into a filter card (form picker, date range, Filter, Clear) and a search card, each in its own GET form, with a consistent 36px control height so the taller native date inputs in 7.0 do not stagger the row. This also fixed an old bug where the Filter button sat inside the bulk-actions POST form, so filter values never reached the URL.

Smaller Changes and Fixes

Added:

  • bulk_mark_spam as a per-form bulk action. bulk_unmark_spam now returns to the per-form tab when used there.
  • An is_spam argument on cf_get_form_submissions() and cf_count_form_submissions(): null for any, true for spam, false for inbox.
  • Daily cleanup crons for stale drafts (cf_cleanup_drafts, 30 days by default) and old analytics rows (cf_cleanup_analytics, 365 days by default). Both unschedule when the plugin is deactivated.
  • A per-form public submission rate limit with the cf_submit_rate_limit filter. The default is unlimited.
  • Webhook signer key rotation: outgoing workflow webhooks carry an X-CF-Key-Id header, and the verifier accepts a callable so the receiver can resolve the secret per key.
  • Per-form analytics authorization through the cf_can_view_form_analytics filter, for narrowing the analytics REST routes to, say, the form’s author.
  • New hooks: cf_payments_orchestrator, cf_payment_completed, cf_form_default_settings, cf_form_templates, cf_draft_ttl_days, cf_analytics_retention_days and cf_action_queue_stale_minutes.

Changed:

  • Migrations run on activation and through upgrader_process_complete, instead of on every admin page load, and return immediately when the schema version already matches.
  • The action queue requeues rows stuck in processing for longer than 10 minutes.
  • The analytics tracker accepts an event_id, so a retried client request does not double-count a funnel event.
  • Schema\Field and Schema\FormSchema have typed properties and strict_types. The public API is unchanged. CI now runs PHP 8.1, 8.2 and 8.3.
  • The WordPress Screen Options dropdown is hidden on every Core Forms admin page, and the persistent Akismet banner on form-edit pages is gone.

Fixed:

  • The autoloader require is guarded with file_exists(), so a partial extraction during an in-place plugin update cannot take the site down. A clear notice appears on the next page load if vendor/ is genuinely missing.
  • Inline validation no longer fires on the first blur of an empty required field. Errors show after the visitor has interacted with the field or tried to submit.

Accessibility:

  • Fullscreen multi-step forms expose aria-current="step" on the active question, aria-hidden on the rest and a polite live region announcing step changes.
  • prefers-reduced-motion turns off the step transitions.
  • Logical-property fixes for the select chevron and the multi-step navigation in RTL layouts.

The Limits

Payments in 4.1 are hosted checkout only. The visitor leaves the site to pay and comes back. An inline card field on the form itself is not in this release.

The headless API issues one key per site, not one per form or per client. Rotating it logs out everyone, so a site serving several external apps should put them behind one proxy.

Analytics access defaults to the same capability as before. The new filter lets you narrow it, but nothing narrows it for you.

Final Remarks

If you have updated, the plugin can now take money and answer requests from outside WordPress, and neither of those changed how an existing form renders. The idea worth keeping is that a paid submission is not a submission until the gateway says so. Everything in the payment design follows from that one rule.

If you are setting up payments for the first time, I would do three things. Keep test mode on until a real webhook has arrived, because the form looks finished long before the webhook does. Use a fixed amount unless the visitor genuinely decides the figure, since a fixed price is one less value to validate. And keep the headless key on a server, never in the browser, because whoever holds the key can submit as your site.

The payments guide walks through each provider, and if a provider behaves differently from what is written here, the support page is where to tell me.

That is all for 4.1. I hope it earns its place on your site.

Build the form. Stop reading.

Every note here came out of a real Core Forms setup. Use CFLAUNCH for 20% off either plan.