Add a file upload to WooCommerce checkout

Published Updated 3 min read

Print shops, embroiderers, sign makers and anyone selling made-to-order work hit the same snag: the order comes in through WooCommerce and the artwork comes in by email. Two streams for one job. On a day with twenty custom orders, each one is another chance to match the wrong file to the wrong order.

WooCommerce Product Add-Ons, the official extension, handles uploads as one input type among many. At $49 a year it also brings date pickers, colour swatches, per-option pricing, a character counter for personalisation fields and a drag-and-drop builder. If you sell three products that each need one file, that is a lot to pay for and maintain.

One checkbox on the product screen and one file input at checkout does the job. The file attaches to the order, and the order screen gets a download link.

Artwork sent by email drifts away from its order

An emailed file has nothing tying it to the order except a subject line, a name match or someone's memory. Some orders collect several threads: the original, a revision, a confirmation. The right version is in there somewhere.

With the file on the order, whoever opens it in wp-admin sees the order and the artwork together, without a trip to a mailbox or a shared drive. The file is there while the order is there. Across fifty custom orders a week, that removes fifty chances for a file to go astray without changing anything about how customers shop.

One checkbox per product, one field at checkout

The product data panel gets a tab with a single checkbox: Require file upload at checkout. Whenever a ticked product is in the cart, a file input appears in the checkout form.

The field is required. Submitting without a file shows an error and the order cannot be placed, so the upload is never skipped by accident, and customers buying products that need no file never see the field at all.

Where the file lives once the order exists

When the order is created, the file goes into the WordPress media library, attached to the order post, and its URL is saved to order meta. It shows under Media in wp-admin but is not publicly visible.

The order edit screen gets a Customer File box with a download link. No separate screen and no extra menu item: anyone who can open the order can download the artwork.

One file per order, and when Product Add-Ons earns its $49

The build adds one upload field per order. A customer with three custom products in the cart uploads one file. For most small custom shops that is the common case: someone ordering an embroidered hat sends their logo once. If you routinely need two files, a follow-up message can add a second field labelled for your workflow.

Buy Product Add-Ons when uploads affect price (a print job with more pages costing more), when many products each need a different upload set-up and a visual builder beats a settings screen, or when you already pay for it for other reasons. File upload comes bundled there, so a separate plugin alongside it makes no sense.

What you get

Included

  • A checkbox on the product data panel to mark a product as requiring a file upload at checkout
  • A file input added to the checkout form when a qualifying product is in the cart, with validation blocking submission if the field is empty
  • The uploaded file saved to the WordPress media library and attached to the WooCommerce order post
  • A download link on the order edit screen so the admin can access the file without leaving wp-admin
  • Works alongside any theme and does not require changes to checkout templates

Not included

  • Per-line-item file uploads when multiple custom products are in the same cart
  • Multiple file uploads per order
  • File type or size validation beyond WordPress media library defaults
  • Artwork review, proof approval, or revision workflows
  • High-Performance Order Storage compatibility: the file meta box uses the classic shop_order screen

WooCommerce Product Add-Ons does the things on the right. This build covers the part most sites use.

Questions

Can I make the upload optional?

Yes. Remove the woocommerce_checkout_process check and the required marker from the field label, and checkout will accept orders without a file.

Does this work with the WooCommerce block checkout?

No. The field hooks into the classic checkout form. Block checkout uses a different extension system, and an upload field there needs a separate approach through the Cart and Checkout Blocks extensibility API.

What file types are accepted?

Whatever WordPress allows by default: images, PDFs, Office documents and similar. WordPress blocks some types for security. Allowing SVG, AI or EPS files needs a filter on upload_mimes.

Can customers see the file they uploaded in their order history?

Not in this build; it is visible in wp-admin only. A hook into woocommerce_order_details_after_order_table that reads the _swfu_upload_url meta would show it on the My Account order page.

Build it in Steem

The button opens the builder with this guide's prompt filled in, so you can change anything before it runs. Nothing is built, and nothing is charged, until you send it.

Build this pluginAbout 55 credits. The free plan includes enough for one.
Read the prompt (452 words)

Build a WordPress plugin called Simple WooCommerce File Upload. Plugin Name: Simple WooCommerce File Upload. Description: Adds a file upload field to WooCommerce checkout for products that require customer artwork or files. Single PHP file. Requires WooCommerce. Add a tab to the WooCommerce product data panel using woocommerce_product_data_tabs. Tab key swfu, label File Upload, icon dashicons-upload. Output the tab panel content using woocommerce_product_data_panels: wrap in a div with id product-data-swfu, output one field using woocommerce_wp_checkbox with id _swfu_require_upload, label Require file upload at checkout, desc_tip false. On woocommerce_process_product_meta, save _swfu_require_upload via update_post_meta: value 'yes' if the POST key is set, 'no' otherwise. Hook into woocommerce_before_order_notes. Loop WC()->cart->get_cart(). For each item, call wc_get_product passing the product_id. If get_meta('_swfu_require_upload') returns 'yes', set a $requires_upload flag to true and break. If the flag is true, output: a div class form-row form-row-wide containing a label with for=swfu_file and text File to upload with a span class required containing an asterisk, and an input type=file id=swfu_file name=swfu_file. After the div, output wp_nonce_field with action swfu_upload and name swfu_nonce. Hook into woocommerce_checkout_process. Loop the cart and check for any product with _swfu_require_upload yes. If found, check whether $_FILES['swfu_file'] is empty or has a non-zero error value. If either condition is true, call wc_add_notice with message Please upload the required file before placing your order and type error. Hook into woocommerce_checkout_order_processed with priority 10, accepting three arguments: $order_id, $posted_data, $order. Verify wp_verify_nonce on $_POST['swfu_nonce'] with action swfu_upload. If the nonce fails or $_FILES['swfu_file'] is not set or has a non-zero error, return early. Call wp_handle_upload passing $_FILES['swfu_file'] as the first argument and ['test_form' => false] as the second. If the result array has a url key and no error key: get the filename via basename of $result['file']. Call wp_check_filetype with $result['file'] to get type. Call wp_insert_attachment with post_mime_type set to $result['type'], post_title set to sanitize_file_name of the filename, post_content empty string, post_status inherit, post_parent set to $order_id, and $result['file'] as the second argument. If the returned attachment ID is not a WP_Error, call wp_generate_attachment_metadata and wp_update_attachment_metadata. Then call update_post_meta on $order_id three times: key _swfu_attachment_id value the attachment ID, key _swfu_upload_url value $result['url'], key _swfu_upload_name value the filename. Add a meta box using add_meta_box: id swfu_order_file, title Customer File, callback swfu_render_order_file_box, screen shop_order, context side. In the callback, get $attachment_id from get_post_meta $post->ID _swfu_attachment_id true. Get $upload_url from get_post_meta _swfu_upload_url true. Get $upload_name from get_post_meta _swfu_upload_name true. If $attachment_id is not empty, output a paragraph containing an anchor with href set to esc_url(wp_get_attachment_url($attachment_id)), target _blank, and link text esc_html($upload_name). Else if $upload_url is not empty, output a paragraph containing an anchor with href esc_url($upload_url), target _blank, and link text esc_html($upload_name). Else output a paragraph with text No file uploaded.