Probo Connect for WooCommerce

Building a Custom Uploader Type for Probo Connect

Every product that goes through Probo needs artwork — a PDF, an image, or sometimes nothing at all. In this guide, we will build a simple working custom uploader type as a standalone WordPress plugin. By the end, your new uploader type will appear in the product settings dropdown and pass the right information to the Probo API when orders are placed.

Probo Connect ships with four built-in ways to handle this: the Probo Uploader (where customers upload their own files), Artwork via URL (where you specify a direct link), Printlane, and No File (for products that don't need artwork). Printlane is a separate service operated by our partner Printlane. To use the uploader, you need a Printlane account.

But what if none of those fit your workflow? Maybe you want to pull files from an external DAM system, let customers pick from pre-approved templates, or integrate with a third-party design tool. That's where custom uploader types come in.

Creating a custom uploader requires at least implementing two specific hooks. One to register it and one to add the final file to a files array to the Probo payload.

At the end of this page, there is a deep dive into how the flow works and which hooks are available to optimize your order flow.

My awesome uploader

Custom uploaders can be created by adding filters or extending the abstract class Probo_Uploader_Type_Abstract. This example contains the filters method, as it is easier to get started and might fit 75% of the use cases. If you want a more robust solution or want to offer it as a plug-in, you should use the class

Registering our type.

First, you should register the uploader type. In this example, we named the uploader awesome_uploader. The key is sanitized during execution. Please only use lowercase letters and an underscore.

add_filter( 'probo_product_uploader_type', 
                   'my_awesome_uploader_register_uploader_type', 
                   10, 
                   2 );
function my_awesome_uploader_register_uploader_type( $types, $product_type ) {
	// Only show for simple/variable products.
	if ( 'normal' !== $product_type ) {
		return $types;
	}
	$types['awesome_uploader'] = __( 'My Awesome uploader', 'probo-custom-uploader' );
	return $types;
}

Adding the data

When the Probo Order Payload is generated, the function probo_order_uploader_type_${uploader_key} will be triggered via apply_filters. In the corresponding filter/function, a correct Probo file array should be returned. This should at least contain a full URI to the file. In the example, the full URI is stored in the order_line metadata _myawesome_custom_data.

Adding the actual data when the order is synchronised

add_filter( 'probo_order_uploader_type_awesome_uploader', 
                   'my_awesome_uploader_image_for_probo_api', 
                   10, 
                   2 );

function my_awesome_uploader_image_for_probo_api( $uploader_data, $product_id, $item_id, $extra ) {
	$item     = \WC_Order_Factory::get_order_item( $item_id );
	$file_url = $item ? $item->get_meta( '_myawesome_custom_data' ) : '';
	if ( empty( $file_url ) ) {
		return $uploader_data;
	}
	return array(
		'files' => array(
			array( 'uri' => esc_url_raw( $file_url ) ),
		),
	);
}

That's it! This should be enough to add a custom uploader to the Probo payload.

Now let's put this all together in a custom plugin. The file should be stored here wp-content/plugins/awesome-uploader/awesome-uploader.php

Our custom plugin

<?php
/**
 * Plugin Name:       Awesome Uploader for Probo
 * Plugin URI:        https://yourdomain.com/
 * Description:       Adds the "Awesome" uploader type to Probo that maps existing order item meta to the API payload.
 * Version:           1.0.0
 * Author:            Your Name
 * Text Domain:       awesome-uploader
 * Domain Path:       /languages
 * Requires at least: 6.0
 * Requires PHP:      7.4
 */
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
add_filter( 'probo_product_uploader_type', 'my_awesome_uploader_register_uploader_type', 10, 2 );
add_filter( 'probo_order_uploader_type_awesome_uploader', 'my_awesome_uploader_map_payload_for_probo_api', 10, 4 );
/**
 * Registers the awesome uploader type in the Probo settings panel.
 *
 * Only shown for simple/variable products ('normal'), not for the Probo Configurator.
 *
 * @param array  $types        Existing uploader types (key => label).
 * @param string $product_type The product panel context ('normal' or 'configurable').
 * @return array
 */
function my_awesome_uploader_register_uploader_type( $types, $product_type ) {
	// Only show for simple/variable products.
	if ( 'normal' !== $product_type ) {
		return $types;
	}
	$types['awesome_uploader'] = __( 'My Awesome uploader', 'awesome-uploader' );
	return $types;
}
/**
 * Maps the saved WooCommerce order item meta to the data structure expected by the Probo API payload.
 *
 * @param array $uploader_data Existing uploader data (empty by default).
 * @param int   $product_id    The product ID.
 * @param int   $item_id       The order item ID.
 * @param array $extra         Reserved for future use (always empty currently).
 * @return array File data to merge into the payload, or the unchanged data if no file.
 */
function my_awesome_uploader_map_payload_for_probo_api( $uploader_data, $product_id, $item_id, $extra ) {
	$item     = \WC_Order_Factory::get_order_item( $item_id );
	$file_url = $item ? $item->get_meta( '_myawesome_custom_data' ) : '';
	if ( empty( $file_url ) ) {
		return $uploader_data;
	}
	return array(
		'files' => array(
			array( 'uri' => esc_url_raw( $file_url ) ),
		),
	);
}

How Uploader Types Work

Probo Connect uses a range of hooks to register uploaders. Internally, it uses an abstract class, Probo_Uploader_Type_Abstract, to register its own uploaders — but you can also use the hooks directly.

Every hook (except the first) is dynamic: it ends in {your_type}, where {your_type} is the key you registered in step 1. Register the type as awesome_uploader, and Probo Connect will call probo_uploader_generate_awesome_uploader, probo_order_uploader_type_awesome_uploader, and so on.

Flow

1. Product admin — register your type

Your type appears in the File Uploader type dropdown on the product edit screen. This is the one shared (non-dynamic) filter. The second argument tells you which panel is rendering: normal or configurable.

add_filter( 'probo_product_uploader_type', 'awesome_register_type', 10, 2 );
function awesome_register_type( $types, $product_type ) {
	// Only offer this uploader on simple/variable products.
	if ( 'normal' !== $product_type ) {
		return $types;
	}
	$types['awesome_uploader'] = __( 'My Awesome uploader', 'awesome-uploader' );
	return $types;
}

2. Add to cart — prepare uploader data

When a customer adds the product to their cart, Probo Connect fires probo_uploader_generate_{your_type}. Whatever you return is stored on the cart item and carried through to the order.

add_filter( 'probo_uploader_generate_awesome_uploader', 'awesome_generate', 10, 1 );
function awesome_generate( $cart_item_data ) {
	// Return any data you need later (specs, a token, a file reference, …).
	return array(
		'awesome_source' => 'https://example.com/artwork.pdf',
	);
}

3. Cart display — show status in the cart

The filter probo_store_cart_render_uploader_{your_type} lets you render upload status or file info in the cart. Return an HTML string. Note: it only fires when step 2 actually produced data.

add_filter( 'probo_store_cart_render_uploader_awesome_uploader', 'awesome_cart_render', 10, 2 );
function awesome_cart_render( $cart_item, $cart_item_key ) {
	return '<span class="awesome-status">' . esc_html__( 'Artwork ready', 'awesome-uploader' ) . '</span>';
}

4. Checkout — persist data to the order

The action probo_save_order_item_meta_{your_type} lets you write uploader data onto the order item. This is where you freeze whatever the API step will need later.

add_action( 'probo_save_order_item_meta_awesome_uploader', 'awesome_save_meta', 10, 3 );
function awesome_save_meta( $item, $uploader_values, $order ) {
	if ( ! empty( $uploader_values['awesome_source'] ) ) {
		$item->add_meta_data( '_custom_data', $uploader_values['awesome_source'], true );
	}
}

Optional: you can also hook probo_validate_checkout_item_{your_type} (filter, receives true + $cart_item) to block checkout when required data is missing. Return false or a WP_Error to stop the order.

5. Order sync — return the file data for the Probo API

The filter probo_order_uploader_type_{your_type} asks your type to return the data sent to the Probo API. Return a files array (or uploaders array). This is the step that actually reaches Probo.

add_filter( 'probo_order_uploader_type_awesome_uploader', 'awesome_map_payload', 10, 4 );
function awesome_map_payload( $uploader_data, $product_id, $item_id, $extra ) {
	$item     = \WC_Order_Factory::get_order_item( $item_id );
	$file_url = $item ? $item->get_meta( '_custom_data' ) : '';
	if ( empty( $file_url ) ) {
		return $uploader_data; // No file — leave the payload untouched.
	}
	return array(
		'files' => array(
			array( 'uri' => esc_url_raw( $file_url ) ),
		),
	);
}

6. Admin order view — render file info

The filter probo_admin_order_render_uploader_{your_type} lets you show file info inside the WooCommerce order admin. Return an HTML string.

add_filter( 'probo_admin_order_render_uploader_awesome_uploader', 'awesome_admin_render', 10, 3 );
function awesome_admin_render( $item, $product, $item_id ) {
	$file_url = $item->get_meta( '_custom_data' );
	if ( empty( $file_url ) ) {
		return '';
	}
	return sprintf(
		/* translators: %s: file URL */
		wp_kses_post( __( 'Print file: <a href="%s" target="_blank">view</a>', 'awesome-uploader' ) ),
		esc_url( $file_url )
	);
}

Product Type Support

Each uploader type declares which WooCommerce product types it supports. You control this via the $product_type argument in step 1:

Value Applies to
normal Standard WooCommerce products (simple, variable)
configurable Probo Configurable products (the custom product type added by Probo Connect)

Return your type from step 1 only for the $product_type values you want to support; for all others, return $types unchanged.

Further reading
  1. Which file options are available
  2. How to serve files