Skip to content

Smart Add-Ons File Upload Token Lifecycle: 4 Easy Stages

Smart Add-Ons file upload token lifecycle: unclaimed transient, claimed at checkout, admin-only download

Developer guide

How does the Smart Add-Ons file upload token lifecycle actually work?

A file uploaded before checkout gets an unclaimed transient token (24-hour TTL) tied to the field ID and session — the token, not the file, is what the add-on field submits as its value. At checkout, woocommerce_checkout_create_order_line_item claims that token against the new order ID, extends it to a month, and writes it into order-item meta as _saue_upload_{field_id}. Downloading it later is an admin-only REST route gated on edit_shop_orders, and every _saue_ prefixed key is stripped from the customer-facing order display by a dedicated formatting filter.

This is not the store-owner guide to turning on a file upload field — that’s the secure file upload guide, and it’s a good one for a checklist of settings. This is the Smart Add-Ons file upload token lifecycle from the code’s point of view: what actually happens between a shopper choosing a file and an admin downloading it, for anyone extending the plugin or building a custom flow against the same mechanism.

Smart Add-Ons file upload token lifecycle: unclaimed transient, claimed at checkout, admin-only download
A token, not a file path, is what actually moves through the cart.

Smart Add-Ons file upload token lifecycle, stage one: an unclaimed token

A file upload field has nowhere to attach data to before the order is created — there’s no order ID yet. UploadTokenStore::create() solves this by generating a UUID and storing the upload’s metadata in a WordPress transient, unclaimed:

public function create( array $data ): string {
    $token = wp_generate_uuid4();
    $data['token'] = $token;
    set_transient( self::TRANSIENT_PREFIX . $token, $data, self::TTL ); // 24 hours
    return $token;
}

The token string — not a file path, not the raw file — is what the field submits as its value alongside every other add-on selection. This matters for anyone building a custom frontend: a file-type field’s “value” in saue_addons is always this token, and validateForField() checks it against the field ID and session ID it was issued for before anything downstream trusts it.

Stage two: claimed at checkout, one time only

CheckoutHooks::saveAddonMetaToOrderItem(), hooked to woocommerce_checkout_create_order_line_item, is where the token becomes permanent. For every file-type field in the submitted selections, it looks up the token and claims it:

public function claim( string $token, int $order_id ): bool {
    $data = $this->get( $token );
    if ( $data === null || ! empty( $data['claimed'] ) ) {
        return false; // already claimed - can't be reused for a second order
    }
    $data['claimed']  = true;
    $data['order_id'] = max( 0, $order_id );
    set_transient( self::TRANSIENT_PREFIX . $token, $data, self::CLAIMED_TTL ); // 1 month
    return true;
}

Claiming is one-way and one-time: a second attempt to claim the same token — from a retried checkout, a duplicated request, anything — returns false immediately because claimed is already set. The order-item then gets a JSON-encoded _saue_upload_{field_id} meta key with the token, label, and file details, extending the token’s transient lifetime from a day to a month in the process. Claiming is stage two of the Smart Add-Ons file upload token lifecycle, and the only stage where the token’s ownership actually changes.

Stage three: reading it back after the order exists

OrderUploadRepository::getUploadsForItem() reads every _saue_upload_* meta key off an order item to list its uploads. One deliberate choice here, documented directly in the code: a file that’s since become unreadable — deleted from disk, cleaned up, wiped on a demo install’s reset schedule — still produces a row, marked available: false, rather than being silently omitted:

// Silently omitting it made a customer's upload disappear from the order
// with no trace once the underlying file was gone - which reads to the
// admin as "this customer never uploaded anything" when they actually did.

That distinction — missing entirely versus present-but-unavailable — is the difference between “nothing to investigate” and “a real support question,” and it’s why the repository never just drops a row on a failed is_readable() check.

Stage four: downloading it is admin-only, by design

OrderUploadController‘s download route gates on capability, not on being the customer who placed the order:

public function permissionCallback( WP_REST_Request $request ): bool {
    return current_user_can( 'edit_shop_orders' ) || current_user_can( 'manage_woocommerce' );
}

There’s no customer-facing “re-download my file” link anywhere in the plugin — a shopper who needs their own upload back has to ask the store, the same as with any other order attachment a store keeps on file. That’s a real, current limitation worth knowing before assuming otherwise, not an oversight to work around silently. It’s also the final stage of the Smart Add-Ons file upload token lifecycle: the token has done its job once the file is safely attached to an order an admin can retrieve.

The last piece is what the customer and admin see in the normal order view: every internal bookkeeping key, including the dynamic _saue_upload_{field_id} one, is stripped by a single prefix-match filter rather than an enumerated list:

add_filter( 'woocommerce_order_item_get_formatted_meta_data', function ( $formatted_meta ) {
    foreach ( $formatted_meta as $id => $meta ) {
        if ( str_starts_with( $meta->key, '_saue_' ) ) {
            unset( $formatted_meta[ $id ] );
        }
    }
    return $formatted_meta;
} );

Prefix match rather than a hardcoded key list is the detail worth noting: it covers every current and future _saue_upload_{field_id} key automatically, for any field ID, without needing to know field IDs in advance.

Common questions

What happens to an unclaimed token if checkout never completes?

Nothing has to happen — it’s a transient with a 24-hour TTL, so an abandoned cart’s upload simply expires and WordPress cleans it up on its own schedule. No orphaned-file cleanup job is needed for the common case, which is by design: the Smart Add-Ons file upload token lifecycle assumes most tokens are never claimed at all.

Can a custom integration issue its own upload tokens?

UploadTokenStore is an internal class, not a public API, so this isn’t a documented extension point today. A headless or custom-checkout integration reusing the built-in upload field still needs to go through the plugin’s own upload endpoint to get a valid token — see the REST API guide for the endpoints that are documented as public.

For the store-owner-facing settings this system sits behind, see the secure file upload guide, and WordPress’s own Transients API documentation (opens in new tab) for how the expiring-storage layer underneath all of this actually works.

Dhyan Software Avatar

Written by

Ready to ship this in your store?

Smart Add-Ons for WooCommerce turns what you just read into a working product page — no code required.

Comments

Leave a comment

Your email is not published.