For most WooCommerce checkouts, customers will be purchasing products and remitting payment to your store. They may also be selecting shipping methods or entering coupon codes.

However, when customers purchase a free item, you may not need all of the fields at checkout if you’re not collecting any payment for shipping. As a result, you may require almost none of the checkout fields.

While WooCommerce will helpfully remove payment method selection for free checkouts, the billing fields are still present and required to place the order:

Standard WooCommerce free checkout

Standard WooCommerce free checkout

If you’re selling free virtual products, such as a free membership with WooCommerce Memberships, you may want simplify this checkout form even more.

Fortunately, it’s really easy to check if the checkout has no cost with the WC()->cart->needs_payment() check. We can use this to take a few actions to simplify the free WooCommerce checkout if no payment is needed, such as conditionally removing some billing fields.

Let’s walk through some steps we can take to simplify the free checkout, and then put it together into a usable snippet.

1. Remove Coupon Forms

First, you probably don’t want the coupon form for your free checkout, as there’s nothing to discount. Using ! WC()->cart->needs_payment() as a check assumes that the checkout total is $0, so there’s no shipping costs or order payment needed. As a result, we can safely remove the coupon input.

// remove coupon forms if you don't want a coupon for a free cart
remove_action( 'woocommerce_before_checkout_form', 'woocommerce_checkout_coupon_form', 10 );

The only instance you may need this form is if you use something like Free Gift Coupons, which isn’t providing a discount with the coupon, so the form could still be needed.

2. Disable Order Notes

You may want to gather order notes from the customer for the free order. However, our goal here is to completely simplify the free checkout, so I’m going to remove that entire “Additional Information” section of the checkout.

We can do so with the woocommerce_enable_order_notes_field filter, as we can set this to false so there are no order notes.

// Remove the "Additional Info" order notes
add_filter( 'woocommerce_enable_order_notes_field', '__return_false' );

3. Unset Unnecessary Fields

Since we don’t need to charge a credit card, we probably don’t need most of the billing fields in this checkout. I’m going to unset some billing fields here, and you can choose with fields you want to remove from the checkout form.

You can find a list of all checkout fields in the WooThemes documentation.

// Unset the fields we don't want in a free checkout
function unset_unwanted_checkout_fields( $fields ) {
    
    // list of the billing field keys to remove
    $billing_keys = array(
        'billing_company',
        'billing_phone',
        'billing_address_1',
        'billing_address_2',
        'billing_city',
        'billing_postcode',
        'billing_country',
        'billing_state',
    );

    // unset each of those unwanted fields
    foreach( $billing_keys as $key ) {
        unset( $fields['billing'][$key] );
    }
    
    return $fields;
}
add_filter( 'woocommerce_checkout_fields', 'unset_unwanted_checkout_fields' );

This is what does most of the work in simplifying the free WooCommerce checkout.

4. Maybe Tweak the CSS?

This one will be up to you to handle, as which fields you’ve removed may affect the layout of your checkout fields. In my case, my “create account” fields are slightly messed up since they’re trying to fit in next to the email field.

New Free WooCommerce Checkout fields

New Billing fields

I could make the email field full width or put some space between these guys to make this look a bit neater. Let’s add some CSS in this snippet so it’s only added when the checkout is free and these fields are removed.

function print_custom_css() {
    echo '<style>.create-account { margin-top: 6em; }</style>';
}
add_action( 'wp_head', 'print_custom_css' );

Putting it Together

As a final note, we’ll wrap all of this code in a check for WC()->cart->needs_payment() so we can be sure we only remove these fields when no payment is needed. You can find the full snippet here to add wherever you keep custom code on your site.

When we’ve added this entire snippet, our free WooCommerce checkout will be greatly simplified, as we’re only asking for name and email:

Updated WooCommerce free checkout

Updated free checkout

Published by Beka Rice

Beka manages WooCommerce product direction for SkyVerge, such as plugin roadmaps, documentation, and sales copy. She typically gets stuck with boring administrative tasks, but likes to write so she has an excuse to spend more time jamming out to anything from The Clash to Lady Gaga.

73 Comments

  1. Great tutorial! Am going to use this for a couple of free Sensei courses I’m planning. Love all your tutorials!

  2. Thanks for this tutorial, used the code with the Code Snippets plugin and had it all set up in just over 5 mins – a great quick fix – thank you!

  3. Hi Beka,

    Great code and very useful – thank you!!! πŸ™‚
    It works great.

    The only issue is that when a product has a price & a coupon is used to get it discounted to $0, the code doesn’t cover this and if I don’t manually refresh the page, the billing fields are still there.
    Maybe you can add something to the code so when the apply coupon button is clicked and as a result the checkout cart amount is refreshed to $0, the snippet will recognize it and will remove the billing fields?

    Thanks again,

    Tom

    • Hey Tom, unfortunately this is because the checkout page is refreshed via ajax (without a page load / refresh), while this code is loaded on the page load instead (which is why the refresh is needed to adjust the fields). Not something I plan to add here since it’s not exactly simple, but it would be possible to do with the help of a developer.

  4. I like to treat my free downloads like products and run them through WooCommerce. A while back I wrote a couple posts explaining that and included something similar to what you presented here but for virtual, downloadable items only. Your removal of the coupon and notes fields is a nice extra touch that I’ll certainly adopt for my store as well … along with some of your nicer coding. πŸ™‚

  5. That works really great and will make it much easier to sign up for Free Memberships. The only problem I’m having is that for some reason it removes these fields for paid subscriptions as well. It works fine for all other products.

    Have any idea how to stop it from removing the fields from a subscription product?

    Thanks

    • How’s the subscription set up? I can’t replicate this — a paid sub has all checkout fields while free cart has none for me.

      • Weird. It may have something to do with the Woo Checkout Field Editor I have installed as well.

        • Probably, the only thing I realized later that I didn’t account for a free subscription that has a signup fee — the cart total is technically reading as “no payment needed” in that scenario even though the sign up fee is due. Is that by chance what you had? If so, I added a note here. If not, then there’s definitely a conflict elsewhere.

          • I just found what the issue was.

            If I deselect the option in Woocommerce Settingts under Subscriptions to allow Subscriptions and Products to be purchased at the same time then a subscription product must go to the cart first.

            This will work fine with the code you provided.

            But if you don’t allow Subscriptions and Products to be purchased at the same time then the Subscription product goes straight to the checkout which I like.

            Then the checkout fields are missing just like a Free Subscription, however when you refresh the checkout page the correct fields appear.

            Does that make sense?

  6. Hi,
    useful code for marketing ideas, thanks!
    I have unexpected behaviour: It works great with free products only, but if you have free and paid products it shows the simplified billing form!

    Did I made something wrong?

    Thanks,
    Bruno

    • Update:
      if I refresh the checkout-page (that show only reduced fields), the complete billing form appears!

      Bruno

      • Hi, I have the exact same issue as you Bruno. If I click to order something which requires payment it still shows the simplified version. However, if I refresh the checkout page then the billing info appears. Did you find a fix for this?

        Thanks,
        Matt

    • I just had the same issue. I will try what you have suggested. Thanks.

    • I have unchecked the checkbox for subscription and product can be purchased simultaneously, but still didn’t work for me. When I go to checkout for a paid subscription, most billing fields are still hidden.

  7. Hi Matt,
    I tried a slightly different code, and this works.

    [Moderated: Please use services like GitHub Gists or Pastebin for long code snippets]
    http://pastebin.com/6p3iZTFA

    I hope that it will help you.
    Thanks,
    Bruno

  8. Thanks for these helpful snippets. I need to go the other direction… we want to offer free membership but we need credit card data for future purchases (stored securely off the servers of course). Do you happen to know how/where to override the WC()->cart->needs_payment() check to force the collection of payment method info at checkout for a $0 product? I feel like I’m on the verge of figuring this out from your article but I’m a little stuck.

    • Whoa! I think I figured it out. Went to file called woocommerce/includes/class-wc-cart.php and changed:

      public function needs_payment() {
      return apply_filters( 'woocommerce_cart_needs_payment', $this->total > 0, $this );
      }

      to:
      public function needs_payment() {
      return apply_filters( ‘woocommerce_cart_needs_payment’, $this->total >= 0, $this );
      }

      Saved that file in my child theme. Need to test but I think it works. Am wondering whether I could do it in my functions file instead of relying on overriding the entire php file. I feel like that would be better practice. Not good enough at php code to know for sure so if you know, I’m all ears for any of your experienced thoughts on this.

      • add_filter( 'woocommerce_cart_needs_payment', '__return_true' );
        should work in your child theme to filter the value instead of changing the value where its set. However, I’d make sure to test this out fully since I know that check is used in a lot of places.

      • I knew I was thinking of a problem when I originally replied and just remembered what it was — many payment processors don’t allow for $0 captures, some allow $0 authorizations (without capture), so I’d recommend checking this with your payment processor.

        • Thanks so much! Not an issue as we are using a plugin from WPLabs called Offline Credit Card Processing. Might be useful to others to know about this…in the case where merchant wants to securely collect credit card data and then process with their own terminal, this plugin seems to solve the issue (I spent a while finding it). Breaks up credit card info so it’s not all stored on the server. So in our case we are not using a real gateway and $0 issue goes away… otherwise, yes, it would be an issue! Thanks very much for the filter code, that is exactly what i needed (php is so finicky I didn’t want to keep getting the white screen of doom as I tried different options). I will test it out and see how it works (don’t worry I’m using a fileserver, not the backend of my WP site πŸ™‚

  9. I am very happy to have found this. Thank you very much.

    Question: Instead of the function triggering when check out doesn’t need a payment, would this work if function was triggered if only on a specific page?

    I’m not great at php, but it seems that…

    if ( ! is_checkout() || ( is_checkout() && WC()->cart->needs_payment() ) ) {

    could be replaced with something such as…

    if ( is_page(’24’) ) {

    The reason I’m asking is that I am looking for a method for a limited checkout to show only on a specific page which uses a shortcode of the One Page Checkout plugin (Prospress).

    My idea is to create a squeeze page format on this single page which allows users to subscribe to a free membership using your Memberships plugin. The user would only need to enter their first name, email, and password. The user could be sent through a sales funnel and if they purchase a premium option down the road, they could make a new purchase on the standard cart form.

    I haven’t seen anything like this available with woocommerce yet and think this would be very powerful for a lot of people. Please let me know your ideas.

    • Hey Dale, you’re definitely on the right track here and this is a really neat idea! One consideration is that customers may have other items in the cart that you’d need these fields for, so you may want to keep the “needs payment” checks in. You could replace each is_checkout() check with your is_page() check instead if you wanted to go this route.

      If you always want this form to be simplified, even if there are other products in the cart, then your approach should work as well.

  10. Very useful. Just what I was finding for.
    Thanks Beca.

  11. Is it possible to remove the part of the checkout page that displays the total, shipping, tax, etc.?

    I have a feature on my website that lets customers request something not listed on my website and we would then charge the customer at time of delivery, so rather than them see “Total $0, Shipping $0, etc.” I would just like the checkout screen to display the billing and shipping fields.

    Thanks!

  12. I love these tutorials. So grateful that you take the time out to answer questions here, Beka. I’m still learning php, so wonder if you can give me a hint about using a particular coupon and not just any 100% off coupon?

    So if I want to only abbreviate the checkout fields for people using coupons with the prefix “beka-…” (but are auto-generated so there’s no way to specify the full coupon ID) how would I do that?

    Thanks in advance!

    • Hmm, this would definitely be far more complex, I don’t have any sample code handy for something like this. Instead of a simple check for needs_payment(), you’d need to hook into the cart object, check for applied coupons, loop through them to check the beginning of the coupon code string for your phrase, then execute your checkout changes if any applied coupon matches this condition.

      We have a function in our framework for checking “string starts with” that may be helpful, but I’m not sure if this is the best way to do it, so you may want to dig into the coupon and cart classes to see if there are any helpful coupon functions.

  13. ermagod — I added your code to snippets and it worked great .. and then I got this error message upon checking out…

    Warning: Invalid argument supplied for foreach() in mytheme/plugins/woocommerce-subscriptions/includes/class-wc-subscriptions-cart.php on line 585

    — when I checked on that line, line 585, here’s what I see –
    foreach ( WC()->cart->recurring_carts as $cart )

    Any chance you can tell me what’s so invalid??

    Thanks and sorry for any panicking.

    • Do you have any custom code around subscriptions or other plugins that use it? I can’t replicate this myself using Subscriptions 1.5 or 2.0 and the completed snippet above, so I’d recommend doing troubleshooting like disabling all plugins / switching themes to try to narrow it down.

      • There was no special tweaking to the subscriptions. I had reset the plugins. Changed out the theme, but still got that same error message on checkout.

        Then I commented out the special CSS tweak, and it works great. No error messages.

        So thanks very much!

  14. Seriously, thank you. This tut will save my clients listeners time and hassle for free music downloads.

  15. If I wanted to switch out looking for the coupon and use a category ID instead – any items in specials for example – to remove those extra billing fields, how would I go about that?

    Instead of this:
    // Bail we’re not on a particular page, or if we’re at checkout but payment is needed
    if ( ! is_page() || ( is_page() && WC()->cart->needs_payment() ) ) {
    return;
    }

    I do this:
    // Bail we’re not on a particular page, or if we’re at checkout but payment is needed
    if ( ! is_page() || ( is_page() && WC()->cart->is_product_category( ‘specials’ ) ) ) {
    return;
    }
    .. and then keep the rest of the code as is??

    • There’s no is_product_category() check available like that on the cart object. You’d need to add a fair bit of additional code to get all items in the cart, get the product objects for each, loop through them, and set / return a flag based on if a product within your category is found. This would also depend on if it should execute when one product in the category is found, or only if all products are in the category. Not too tough, but something I’d recommend hiring out to Codeable if you’ve not done it before.

  16. Do you have plugin for this or know of anyone that has a plugin for this? I don’t know how to work with code. : ( Thanks in advance!

  17. Hi Beka,
    Thank you for this code. I used it in its entirety in Code Snippets and it is working great as far as purchasing something for free, however, I notice that sometimes when I try to purchase something that is not free, the same result shows as for free items (that is, only a couple of fields to fill), then when I try to go to pay, it says that I have not filled in the required fields (and lists all of the other fields that would normally be there).

    I wonder if this is because I did not wrap all of the code in a check for WC()->cart->needs_payment(). Not sure how to do that.

    I further discovered that if I simply refresh my page, the fields show up. However, most customers would probably move on once they see the error message so I would love to fix it. I read through some other comments and believe that I saw a couple others with a similar problem but I have not been able to find the solution. Please help.

    Thank you again for this code Beka.

    Sean

    • Can you give me more details here? I cannot replicate this at all so without that can’t know how this is happening.

      1. I add the free item to cart
      2. I go to checkout, I see the simplified form
      3. I add any paid item to the cart
      4. I go to checkout, I see all fields right away, no refresh or changes required

      What are you doing when you add the paid item? Is your checkout page cached in any way?

  18. Hi Beka,

    Thank you for your response. I just discovered that if I click on “View Cart” after I had added the priced item, then Proceed to Checkout (from within the cart), all the fields show up.

    However, if I add the priced item and them click on the Proceed to Checkout option (without first viewing the cart) from within my header, only the skeletal fields show. Is there a setting I need to change within my header?

    • Interesting, so is “Proceed to checkout” just a link to the checkout page, or is it generated by a widget or something (ie the mini cart widget)?

  19. Hi,

    Its cool and very useful! How can I supplement this code for shipping fields?

    Thank you

    Kind regards
    Peter

    • I tried this code but doesnt work:

          $billing_keys = array(
              'billing_address_2',
              'billing_country',
          );
      
          $shopping_keys = array(
              'shopping_address_2',
              'shopping_country',
          );
      
          // unset each of those unwanted fields
          foreach( $billing_keys, $shopping_keys as $key ) {
              unset( $fields['billing'][$key] );
              unset( $fields['shopping'][$key] );
          }
      
          return $fields;
      }
      
      • Hey Peter, If you don’t need to ship your products, they can be marked as “virtual”. If the order is only virtual items, no shipping fields are shown.

        • Hey Beka,

          It’s not virtual product just I would like to remove some shipping fields (address2 and country). Could you help me?

          Kind regards
          Peter

  20. Is there no other way to offer a free registration form? This solution will likely scare off a good proportion of users when they see:

    the URL and page title indicate the user is on a checkout page
    terms like the heading “Billing Details” and “Your order”
    a button with text “Place Order”

    Woocommerce Memberships gets so many things right – How can the integration of a proper free registration be missing?

    Is it possible to conditionally change the template used for the checkout page when no payment is required?

    • Hi Jason, I’m afraid that Memberships doesn’t modify the purchasing process at all since it’s tied to WooCommerce products for access. This is why the standard checkout process is used — Memberships is only checking what’s purchased when an order is created, not modifying that purchase process itself, so it doesn’t modify a free checkout.

      We are looking into some changes on how access is granted for version 1.7, and the possibility to allow access from user registration is one thing we’re considering for this version. I’d recommend voting for this feature so you can stay in the loop for updates πŸ™‚

      If you wanted to conditionally change the checkout further when no payment is required, you can definitely do so! This needs_payment() check used above could be used for other hooks on the checkout page (such as woocommerce_pay_order_button_text), or to translate the text you’d like to change.

  21. Hey Beka,

    I’ve used this snippet for a good while now, so great. But today I see there’s a fatal error. I’m using code snippets plugin to use your own code and see this message:

    [19-Jun-2016 18:36:31 UTC] PHP Fatal error: Call to undefined function is_checkout() in … eval()’d code on line 4

    Line 4 =
    if ( ! is_checkout() || ( is_checkout() && WC()->cart->needs_payment() ) ) {

    I used phpcodechecker to see if I could get some info on what the offense was in line 4 but it said that there were no issues.

    Would there be any other reason why I would get the error other than something with the code in line 4? Some kind of plugin clash?

    Thanks – I love this site a lot, and your work especially.

    • hey Londrina, might have been something to do with a WooCommerce update? The function is_checkout() would only cease to exist if that wasn’t active at some point. I’ll update the gist to harden for this use case, but I’d just recommend deactivating the Code Snippet, then re-activating it so long as WC is active πŸ™‚

  22. OK well it cleared up the WC icons in my dashboard when I did this. And the fatal errors seem to have stopped. Thanks again!

  23. I just wanted to thank you for this article. I am still in the process of testing all scenarios, but I now have it working the way I want due to all the snippets of code. It was a combination of Beka and Bruno’s modifications.

    I wanted to display the billing fields when customer selected a free and paid product together. That was not working with the original code. The billing fields would only come up if a customer only added products with a price (no free items). But I need to be able to capture the billing address for paid items for tax purposes. So, I made a few mods to Bruno’s code and it seems to be working the way I want. A few more tests and I am hoping to make the change tomorrow.

    Thanks again!

  24. Hello Beka,

    Thank you for this wonderful tutorial!

    One thing I noticed was that with woocommerce subscriptions I was getting an error. The cart_needs_payment inside woocommerce subscriptions plugin was throwing the following error:

    Warning: invalid argument supplied for each () in /path-to/wp-content/plugins/woocommerce-subscriptions/includes/class-wc-subscriptions-cart.php on line 819

    Warning: Cannot modify header information – headers already sent by (output started at /path-to/wp-content/plugins/woocommerce-subscriptions/includes/class-wc-subscriptions-cart.php:819) in /path-to/wp-content/themes/path-to-theme/class.frontend.php on line 328

    It appears that adding WC()->cart->total check solves this problem. Like this:

    if ( function_exists( ‘is_checkout’ ) && ( ! is_checkout() || ( is_checkout() && WC()->cart->total && WC()->cart->needs_payment() ) ) )

    Credit for the solution goes to Gennady Kovshenin.

    I’ve taken your code with this small modification and some small cosmetics and turned it into a custom plugin. Would this be of interest to anyone?

    All best,
    Goran

    • This snippet fixed an error when using a coupon with WC Subscriptions for me. However, i’m still getting the credit card form appearing on a free product. It won’t let the use checkout without adding a credit card.

  25. Hi! Beka,

    Is it possible to have “Free” text in place of “$0.00” in cart and checkout pages in woocommerce. I can show the same in single and archive pages but not in cart & checkout pages. Will this change effect the process?

  26. OMG… let me tell you. as a blogger with little to no e-commerce or PHP experience, this article paired with the Code Snippets advice has really helped me! In less then 30 seconds I was able to get my free downloads through the cart with just a name and email, exactly as I wanted. THANKS SO MUCH!

  27. Hi there.

    I have a woocommerce site and I have seen on some Facebook ads that the product is absolutely free, All they have to do is pay for shipping. Where do I activate that in my wordpress woocommerce?

  28. I’ve added the code above and it was working great for awhile, but it seems that a recent update has caused the credit card form to appear on the page preventing users from placing an order without having to enter their credit card details.

    Thoughts…a snippet to fix?

  29. Hi. I am looking to use woocommerce for payments in our bricks and mortar store. Meaning that if someone buys a t-shirt from us in store, we don’t need address details, billing address etc.

    I know SQUARE works with woocommerce, the issue is that we are in the United Kingdom.

    Can you help at all? Will the codes above help?

  30. This was perfect, thank you so much.

  31. Hi,
    where should i fill in this code?

    I don’t find anything like WC()->chart->needs_payment()

    Can you give me the path please?

    Thank you πŸ™‚

    Philipp

  32. Wonderful and very usefull snippet!
    I am using it for image-upload as virtual product, so that customers can upload a picture at a later moment (they have to insert order# and email at this time only) and the data are all in my woocommerce (Version 2.6.14 ).

    The only problem I still do encounter is that the status for this product ‘upload my picture later’ is automatically set to ‘completed’ but I would like it to automatically be set to status ‘image’, wich would ease the workflow tremendously.

    Is there a snippet for this to append to your code?

    • Hey Thorsten, since “image” isn’t a default WooCommerce status, you’d likely need customization to manage / automatically change statuses in this case.

      • Hi Beka,
        I had figured that out before… and by reading your blog and comments I stumbled upon the ‘snippets-plugin’, wich helped cleaning-up my child-theme’s function.php πŸ˜‰
        By using ‘WooCommerce Advance Order Status-plugin’ I had already implemented the new ‘image’-status…
        but can’t work out how to automatically change status
        (in tis case with your snippet-if possible) not to ‘processed’, but to ‘image’.
        Hope you can help.

  33. The script was not working for me as it was always hiding the fields, even if the total amount of the cart was not 0. I solved using “WC()->cart->cart_contents_total != 0” instead of “WC()->cart->needs_payment()”.

  34. Just curious… you mentioned that you could make the email input field full width – How would you do that?
    Do you have to give it a custom CSS class first?

  35. Hi,

    I am using the 1 page checkout plugin if that matters.
    I sell both free and paid products so I can’t alter my checkout template to remove the cart contents and total price as that is required for the paid products.

    I have a checkout page that sells registration to my BuddyPress site using WooCommerce and I have the Woo premium plugin checkout fields that I use to remove all the unnecessary fields but it still shos the cart contents and total of $0 which is disconcerting for customers.

    Here is the page I’m talking about:

    https://mydisabilitymatters.club/join-mdm-club-personal1/

    Is there code that can remove showing the cart contents and the cart total, but just for free products?

    Thanks for any help you can give.

    Dale.

Comments are closed.

Error: Please enter a valid email address

Error: Invalid email

Error: Please enter your first name

Error: Please enter your last name

Error: Please enter a username

Error: Please enter a password

Error: Please confirm your password

Error: Password and password confirmation do not match