Complete Documentation & User Guide
Free v1.1.0 · Pro add-on v1.1.0
Try searching with different keywords
NDV Product Image Upload for WooCommerce lets your customers — including guests, with no account required — upload and crop custom images and PDFs on a product page before adding the product to the cart. Every rule is set per product: how many files are required, the maximum file size, which formats are allowed, minimum and maximum image resolution, the crop shape and aspect ratio, and the upload button’s text and colour.
The free plugin is published on WordPress.org and is fully functional on its own. A separate Pro add-on extends it with bulk operations, settings import, a security-log dashboard and an Elementor widget. Throughout this documentation, anything that needs the Pro add-on is marked (Pro).
Free plugin 1.1.0
woocommerce_is_sold_individually filter, so it never overrides a product you have already marked “Sold individually”.Pro add-on 1.1.0
Requires Plugins header still referenced the base plugin’s pre-rebrand slug, which made WordPress 6.5+ report the dependency as missing and block activation.Before you install: WooCommerce must be installed and active — the plugin will not run without it. The minimum supported versions are WordPress 5.9, PHP 7.2 and WooCommerce 3.5. The Pro add-on additionally requires the free plugin to be installed and active; installing Pro on its own does nothing.
After activation, go to NDV Image Upload in your WordPress admin sidebar. The settings screen opens on Default Settings. With the free plugin alone you get five tabs; the Pro add-on adds three more, for eight in total:
The three Pro tabs only appear on a licensed site.
Start on the Default Settings tab and set the values you expect to use most often. Getting these right first saves time, because they pre-fill the form every time you add a new product configuration.
Two things worth knowing:
Default Settings and their out-of-the-box values
upload_max_filesize and post_max_size are the real ceiling.Use the Add Configuration tab to give an individual product its own rules:
With the Pro add-on you can do all of the above for many products at once on the Bulk Operations tab.
Important: the upload interface appears on a product only when that product has its own saved configuration and that configuration is enabled. A product you have never added on the Add Configuration tab shows no upload interface at all, whatever your Default Settings say. The one exception is the button text and colour: if you leave those blank on a product, the values from Default Settings are used instead. External and affiliate products are not supported.
Site-wide options that apply to every product with uploads enabled.
The starting values used whenever you create a new product configuration. They do not enable uploads anywhere on their own.
Give an individual product — or an individual variation — its own upload rules.
Review, edit and remove everything you have configured so far.
Decide what happens to your data if the plugin is ever removed.
Three additional tabs supplied by the Pro add-on. They appear only when Pro is installed and licensed.
wp-content/uploads/custom_product_images/ and are deliberately kept out of the media library, so they will not appear in your Media screen.Compatibility. The plugin is designed to work with any properly coded WordPress theme, and has been used with Twenty Twenty-One, Twenty Twenty-Two, Twenty Twenty-Three and Storefront. It supports both the classic WooCommerce cart and checkout and the newer WooCommerce Blocks cart and checkout, and is compatible with High-Performance Order Storage (HPOS).
Two limitations worth noting: external and affiliate products are not supported, and no server-side image processing is performed — the plugin does not resize, compress, watermark or convert uploads, and does not add them to the media library.
The free plugin exposes 8 filters and 5 actions. This is the same surface the Pro add-on is built on, and it is treated as stable public API — names and signatures are not changed without a major release, because live shops and the add-on both depend on them.
Everything is prefixed cpiu_. That prefix predates the plugin’s rename and is permanent: options, order-item meta, hooks and nonces all key off it.
// Render extra fields on the Global Settings screen.
do_action( 'cpiu_global_settings_fields', $global_settings );
// Render extra fields on the per-product configuration form.
// $args['context'] is 'add' or 'edit' (and 'bulk' when Pro is active).
// In the 'add' context, $args['defaults'] also holds the Default Settings array.
do_action( 'cpiu_config_form_fields', $args );
// Fires after each file is stored successfully (3 call sites).
do_action( 'cpiu_file_uploaded', $upload_result, $product_id, $user_id );
// Fires on EVERY upload attempt, success or failure - the audit hook.
// $log_entry keys: timestamp, user_id, ip_address, product_id,
// status, success, filename, error_message
do_action( 'cpiu_upload_attempt', $log_entry );
// Daily cron event. Two core callbacks are already attached.
do_action( 'cpiu_cleanup_guest_uploads' );Example — log every failed upload to your error log:
add_action( 'cpiu_upload_attempt', function ( $entry ) {
if ( empty( $entry['success'] ) ) {
error_log( sprintf(
'CPIU upload failed for product %d: %s',
$entry['product_id'],
$entry['error_message']
) );
}
} );The free plugin fires cpiu_upload_attempt but stores nothing. The Pro add-on hooks it to build the Security Logs dashboard; you can hook it yourself instead.
// Register an additional admin tab.
// $tabs is a map of slug => array( label, icon, callback ).
apply_filters( 'cpiu_admin_tabs', $tabs );
// Extend or adjust global settings as they are saved.
apply_filters( 'cpiu_save_global_settings', $sanitized, $raw );
// Attach your own keys to a per-product configuration during sanitisation.
apply_filters( 'cpiu_sanitize_configuration', $sanitized, $config );
// Override the resolved configuration for a product on the front end.
apply_filters( 'cpiu_product_config', $config, $product_id );
// Return false to suppress the default front-end preview.
apply_filters( 'cpiu_show_image_preview', true, $product_id, $config );
// Extend the upload allowlists.
apply_filters( 'cpiu_allowed_mime_types', $default_mime_types );
apply_filters( 'cpiu_allowed_extensions', $default_extensions );
// Adjust how the client IP is resolved (proxies, CDNs).
apply_filters( 'cpiu_client_ip', $ip );Example — add your own admin tab:
add_filter( 'cpiu_admin_tabs', function ( $tabs ) {
$tabs['my-reports'] = array(
'label' => __( 'Reports', 'my-plugin' ),
'icon' => 'dashicons-chart-bar',
'callback' => 'my_plugin_render_reports_tab', // receives array $context
);
return $tabs;
} );Example — store your own key alongside each product configuration:
add_filter( 'cpiu_sanitize_configuration', function ( $sanitized, $raw ) {
$sanitized['my_key'] = isset( $raw['my_key'] )
? sanitize_text_field( $raw['my_key'] )
: '';
return $sanitized;
}, 10, 2 );⚠ Security note: cpiu_allowed_mime_types and cpiu_allowed_extensions control what the upload validator will accept. Widening them — adding SVG, for instance — bypasses a deliberate restriction and is a security decision, not a convenience tweak.
The front-end markup uses stable, prefixed class names you can target from your theme’s stylesheet:
.cpiu-container /* wrapper around the whole upload area */
.cpiu-upload-button-container
.cpiu-upload-btn /* the upload button itself */
.cpiu-image-preview-section /* "Your Uploaded files" panel */
.cpiu-no-images-message /* empty state */
.cpiu-image-item
.cpiu-image-wrapper
.cpiu-delete-btn
.cpiu-modal /* upload modal */
.cpiu-modal-content
.cpiu-close-btn
.cpiu-done-btn
.cpiu-crop-hint
.cpiu-progress-wrapper
.cpiu-spinner
.cpiu-cart-thumbnails /* cart */
.cpiu-cart-image-container
.cpiu-order-item-images /* customer order view and emails */
.cpiu-order-image-container
.cpiu-admin-order-item-images/* admin order screen */The button and progress-bar colours are driven by the per-product button colour setting, so change that in the admin rather than overriding it in CSS.
Registered asset handles, if you need to dequeue or depend on them: cpiu-frontend-multi-product, cpiu-cropper, cpiu-admin-multi-product, cpiu-admin-notices.
JavaScript
The plugin does not emit custom DOM events, and there is no documented JavaScript event API — if you have seen cpiu:upload:start or similar listed elsewhere, those events do not exist and never fired. Integrate through the PHP hooks above.
Two globals are available on a product page for reference:
// Localised configuration for the current product.
cpiu_params // nonce, product_id, ajax_url, and the resolved config
// The cropper, exposed as a promise-based helper.
window.CPIUCropper.open( /* ... */ ).then( function ( result ) { /* ... */ } );Uploads are posted as multipart/form-data to the cpiu_upload_mixed_files AJAX action, with images and PDFs sent together in a single request as native binary blobs.
Important: always extend through these hooks rather than editing plugin files directly, so your work survives updates. If you need something the current hooks cannot reach, ask us — the right answer is usually a new hook added to the free plugin, which then becomes available to everyone.
woocommerce_after_add_to_cart_button hook the interface attaches to.upload_max_filesize / post_max_size..php or .svg.wp-content/uploads/custom_product_images/..htaccess is ignored there.Debug mode: set WP_DEBUG and WP_DEBUG_LOG to true in wp-config.php to capture detailed errors in wp-content/debug.log. When reporting a problem to us, that log plus the exact error shown to the customer is by far the most useful thing you can send.
Between 1 and 50 files, set per product. The default is 9.
One thing to be clear about: this is an exact count, not an upper limit. If a product is set to 3 files, the customer must supply exactly three before they can add it to the cart — not one, not five. Images and PDFs count together toward that total, so a product set to 3 can take two images and one PDF.
JPG, JPEG, PNG, GIF, WebP and PDF.
PDF is supported but switched off by default — enable it in Default Settings or on the individual product. You can allow any combination of the six types per product.
Every upload is checked three ways: the extension must be on the allowlist, the sniffed MIME type must match, and images must pass a signature check. Renaming a file to .jpg will not get it through. SVG is deliberately not supported, because SVG can carry executable script.
Yes. Every product — and every individual variation — can have its own rules: the number of files required, the maximum size per file, which formats are allowed, minimum and maximum image resolution, whether the shape selector is offered, a fixed cropping ratio, the button text and colour, and whether the quantity is locked to 1.
Products you have not configured show no upload interface at all. With the Pro add-on you can apply a set of rules to many products in one pass on the Bulk Operations tab.
Uploaded files are stored in wp-content/uploads/custom_product_images/, outside the media library, with randomised filenames and non-executable permissions. Each file is validated by extension, sniffed MIME type, image signature, a double-extension scan and a scan for executable content before it is stored. The directory gets a deny-all .htaccess, and files are only ever served through a tokenised endpoint that verifies an HMAC of the filename.
Recording those attempts is a Pro feature: the free plugin fires an audit action on every attempt but stores nothing, while the Pro add-on’s Security Logs tab keeps the most recent 1000 entries with success and failure counts.
On Nginx, .htaccess files are ignored entirely, so the bundled protection does nothing. Add this to your server block:
location ^~ /wp-content/uploads/custom_product_images/ {
deny all;
return 403;
}Legitimate access still works, because the plugin serves files through its own tokenised endpoint rather than by direct path.
Yes to both. The plugin is compatible with WooCommerce High-Performance Order Storage (HPOS), including the order-image cleanup routine.
It also supports both checkout styles: the classic WooCommerce cart and checkout, and the newer WooCommerce Blocks cart and checkout. Uploaded files are attached to the order and shown to the customer either way.
Partly with the free plugin, fully with Pro.
The free plugin includes a one-way Export Settings Backup button on the Uninstall Preferences tab, which downloads your global settings, default settings and all product configurations as a JSON file. Taking that backup before a big change is a good habit.
Importing that file back — to restore it, or to copy a configuration to another site — requires the Pro add-on’s Import/Export tab. Pro re-validates every section through the free plugin’s own sanitiser before writing anything, and rejects a file whose product configurations are all unusable rather than silently overwriting your settings with nothing.
It works with any properly coded WordPress theme, and has been used with Twenty Twenty-One, Twenty Twenty-Two, Twenty Twenty-Three and Storefront. The interface picks up your theme’s styling, and the button colour is yours to set.
The one thing to watch for is a theme that replaces WooCommerce’s single-product template and drops the woocommerce_after_add_to_cart_button hook — the upload interface attaches there. If the button does not appear, test with a default theme to confirm that is the cause. With the Pro add-on you can sidestep the issue entirely by placing the Elementor widget wherever you want it.
You choose. The Uninstall Preferences tab offers two options: keep all data (the default, and the right choice for a temporary deactivation) or delete all plugin data on uninstall.
Files that are still referenced by an order are preserved either way, so removing the plugin will not strip artwork from orders you have yet to fulfil. Export your settings first if there is any chance you will reinstall.
Note that the upload folder name, custom_product_images, is fixed and will not change between releases — URLs already stored against your orders and already sent in order emails depend on it.
woocommerce_is_sold_individually filter, so it never overrides a product you have already marked “Sold individually”.Requires Plugins header still referenced the base plugin’s pre-rebrand slug, which caused WordPress 6.5+ to report the dependency as missing and block activation.Open Plugins in your WordPress admin. The free plugin is listed as NDV Product Image Upload for WooCommerce and the add-on as NDV Product Image Upload — Pro; each shows its version number beneath the name. Both are currently at 1.1.0.
The free plugin updates through WordPress.org like any other directory plugin. The Pro add-on updates through your licence.
This page is the complete setup guide and technical reference for both the free plugin and the Pro add-on.
Direct help from our team with installation, configuration and troubleshooting. Pro customers are supported by email; free-plugin users are welcome to email us too, and can also use the WordPress.org support forum.
Learn more about our products, services, and latest updates.
Visit Nowdigiverse⟶
Report security vulnerabilities through responsible disclosure.
Report Security Issue⟶
We welcome feedback and contributions from the WordPress community. The free plugin is published on WordPress.org, follows WordPress coding standards, and exposes a documented hook surface so developers can extend it without editing plugin files.
Contributing: feature requests and general feedback are welcome through our support channels or the WordPress.org support forum. If you need a hook that does not exist yet, tell us — we would rather add it to the free plugin, where everyone benefits, than have you patch the plugin locally.
Security reports: please report suspected vulnerabilities privately by email rather than in a public forum, and give us a reasonable window to ship a fix before disclosing.
We are committed to understanding your requirements and crafting a tailored solution that aligns with your goals.
Enter your details and someone from our team will reach out to find a time to connect with you.
Tell us what you need — we’ll match you with the right expert.