Renaming the WooCommerce Place Order Button
The filter works until a payment gateway decides otherwise. Gateways carry their own button label and set it after your callback has run, which is why the wording reverts the moment a shopper picks a different method.
Your filter is not the last word on this button. woocommerce_order_button_text supplies the default label on the classic checkout, and a payment gateway is free to replace it with wording of its own. The result is a rename that holds for bank transfer and disappears for the card method, which reads as an intermittent bug and is actually two systems doing exactly what they were built to do.
The simple form
One argument, one return:
add_filter( 'woocommerce_order_button_text', 'ti_order_button_text' );
function ti_order_button_text( $text ) {
return __( 'Confirm and pay', 'your-textdomain' );
}
WooCommerce escapes the value when printing it, so returning a plain translated string is correct. Returning markup does not produce a styled button; it produces visible angle brackets.
Wrapping the string in a translation function matters more here than on most labels, because this is the last thing a shopper reads before committing. A hard-coded English string on a multilingual store is a conversion problem, not a cosmetic one.
Why gateways win
A payment gateway can declare its own button label, and for good reason: a method that sends the shopper to an external site to authorise payment should not promise that clicking places the order. "Proceed to PayPal" is more honest than "Place order" when the next screen is somewhere else entirely.
When a gateway supplies that wording, it is used for that method. Your filter still runs and still provides the default for every method that does not. This produces the reported symptom exactly:
- Offline methods such as cheque or bank transfer show your wording, because no gateway label exists.
- A redirect gateway shows its own wording.
- Switching between methods on the checkout swaps the label without a page load, because the button is re-rendered with the payment fields.
If the wording must be identical across all methods, the change belongs in the gateway's own settings where one exists, not in a filter that the gateway is entitled to override.
The other two buttons
Three buttons look similar and come from different places:
| Button | Where | Source |
|---|---|---|
| Place order | Classic checkout | woocommerce_order_button_text |
| Pay for order | An existing unpaid order | woocommerce_pay_order_button_text |
| Place order | Checkout block | Block setting, no PHP filter |
The middle one catches people out on stores that email payment links for manually created orders. That page is a different template, reached from an order that already exists, and the checkout filter has no effect on it. Filtering both is two lines rather than one, and skipping the second leaves an inconsistency that only appears in a flow nobody tests.
Making the wording honest
A rename is cheap, which makes it easy to do carelessly. Two rules keep it useful:
- Say what the click does. If the next screen is an external authorisation page, the button should not claim the order is being placed. If it genuinely completes the purchase, "Place order" is already accurate and changing it to something vaguer costs clarity.
- Do not promise a state the store cannot deliver. "Pay now" on a store whose only method is bank transfer is wrong: nothing is paid at that moment, and the order will sit on hold until a transfer arrives. That mismatch surfaces later as support mail asking why nothing happened, the same confusion described under WooCommerce point of sale and payments.
When the filter appears dead
Work through these before rewriting the callback:
- The checkout is the block, not the shortcode. This is now the common case on new stores, and no PHP filter on this hook applies. The same boundary governs checkout fields.
- A gateway is supplying the label for the method you happen to be testing with. Switch to an offline method and see whether your wording appears.
- A page cache is serving old markup. The checkout should never be cached; if it is, this is one of the smaller problems it causes. Clear it following clearing the WordPress cache.
- A theme overrides the checkout template and prints a hard-coded button. Switch to a default theme for one page load to rule it out.
Verify with two methods and one unpaid order
Load the checkout with an offline method selected and confirm the wording. Switch to a redirect gateway and note what the label becomes — if it changes, that is correct behaviour, not a failure. Then open an unpaid order through its payment link and check the third button.
If the button is missing rather than mislabelled, the problem is not this filter and the trail starts at checkout not working.
Frequently asked
- Because gateways can carry their own button label. A gateway that sets its order button text supplies the wording for the method it handles, and that value is used in place of the generic one your filter returned.
- That page uses a separate filter, woocommerce_pay_order_button_text. It is a different template reached from an order awaiting payment, so the checkout filter never runs there.
- No. The block checkout builds its own place order button and does not read this filter. Changing the wording there is a block-level setting rather than a PHP filter.