Skip to content
Back to skills

Wc Downloadable Products

ASecurity

Implement, extend, or audit WooCommerce downloadable products and customer download access. Covers WC_Product_Download, stable download IDs, product CRUD, approved directories, the customer-download permission table and WC_Customer_Download CRUD, order-based grants and safe regeneration, limits and expiry, My Account and REST reads, bearer download URLs, download methods, logging, partial requests, and protected file storage. Use for _downloadable_files, wc_downloadable_product_permissions(),...

  • 24 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 5, 2026
securityrustphpsqldebugginggitapisecurity

Works with

  • api

Security analysis

A100/100

Pro scans all 3 files and shows the line behind each finding

Scanned September 21, 2026

npx -y skills add Lonsdale201/wp-agent-skills --skill wc-downloadable-products --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Wc Downloadable Products?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Wc Downloadable Products
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/lonsdale201-wc-downloadable-products/badge)](https://www.skillsdirectory.com/skills/lonsdale201-wc-downloadable-products)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: wc-downloadable-products
description: Implement, extend, or audit WooCommerce downloadable products and customer download access. Covers WC_Product_Download, stable download IDs, product CRUD, approved directories, the customer-download permission table and WC_Customer_Download CRUD, order-based grants and safe regeneration, limits and expiry, My Account and REST reads, bearer download URLs, download methods, logging, partial requests, and protected file storage. Use for _downloadable_files, wc_downloadable_product_permissions(), wc_downloadable_file_permission(), WC_Download_Handler, missing/duplicate/expired downloads, private digital files, or code that grants and revokes WooCommerce downloads.
metadata:
  wp-skills-author: "Soczó Kristóf"
  wp-skills-contact: "mailto:lonsdale201@hotmail.com"
  wp-skills-plugin: "woocommerce"
  wp-skills-plugin-version-tested: "11.0.0"
  wp-skills-php-min: "7.4"
  wp-skills-last-updated: "2026-08-05"
---

# WooCommerce downloadable products

Use WooCommerce's product and customer-download objects. A downloadable product definition is not itself customer access, and a permission row is not sufficient unless the related order currently permits downloads.

## Start with the three-layer model

| Layer | Canonical representation | Answers |
|---|---|---|
| Product file | `WC_Product_Download` in `$product->get_downloads()` | What file can this product deliver? |
| Entitlement | `WC_Customer_Download` | Who may download which stable file ID, through which order, until when/how often? |
| Delivery | `WC_Download_Handler` | Does this request pass every check, how is the file served, and is the attempt counted? |

Never infer access from `_downloadable_files`, product ownership, or a permission row alone. Resolve the actual order and use its `is_download_permitted()` gate.

## Choose the task path

- Creating or changing product files: use product CRUD and preserve existing download IDs.
- Granting after purchase: let Woo's processing/completed lifecycle call `wc_downloadable_product_permissions()`.
- Adding one deliberate entitlement: call `wc_downloadable_file_permission()` with a real product and order.
- Regenerating an order: delete that order's existing permissions with the customer-download data store, then force the canonical grant.
- Displaying customer downloads: use `wc_get_customer_available_downloads()` or order downloadable-item APIs.
- Debugging security, storage, limits, REST, tracking, or duplicate rows: load [references/core-download-contract.md](references/core-download-contract.md).

## Create or update a downloadable product

```php
$product = new WC_Product_Simple();
$product->set_name( 'Field guide' );
$product->set_regular_price( '29.00' );
$product->set_virtual( true );
$product->set_downloadable( true );
$product->set_download_limit( 3 ); // -1 means unlimited.
$product->set_download_expiry( 30 ); // Days; -1 means no expiry.

$file = new WC_Product_Download();
$file->set_id( wp_generate_uuid4() );
$file->set_name( 'PDF guide' );
$file->set_file( $protected_download_url );

$product->set_downloads( array( $file ) );
$product->save();
```

`set_downloads()` validates new or changed paths. Local files must exist, use an allowed type, and pass Approved Download Directory checks when that feature is enabled. Remote absolute URLs are accepted without a remote existence request; shortcode providers must enforce their own validation.

WooCommerce 11.0 correctly resolves root-relative content paths against relocated/custom `WP_CONTENT_DIR` locations and requires a path-segment boundary. Do not assume content is literally `/wp-content`, slice a hard-coded 11 characters, or treat `/application` as if it were under `/app`. Store paths through product CRUD and let `WC_Product_Download` resolve/validate them.

For an existing file, reuse its ID:

```php
$downloads = $product->get_downloads();
$file      = $downloads[ $download_id ];
$file->set_name( 'Updated label' );
$file->set_file( $new_path );
$product->set_downloads( $downloads );
$product->save();
```

Since WooCommerce 3.3, generated download IDs are UUIDs, not file hashes. Replacing an ID strands old permission rows; preserving it intentionally moves existing entitlements to the new file. Treat that as an access migration decision, not a cosmetic edit.

## Grant access through the order lifecycle

Woo hooks `wc_downloadable_product_permissions()` to processing and completed statuses. It skips an already-granted order unless forced, respects the "grant after payment" setting for processing orders, and creates one permission per line-item file.

```php
// One explicit file permission. Product limits are multiplied by quantity.
$permission_id = wc_downloadable_file_permission(
	$download_id,
	$product,
	$order,
	$item->get_quantity(),
	$item
);
```

Do not call this repeatedly as an idempotent operation: the table has no unique constraint. Likewise, `$force = true` does not remove old rows.

Safe full regeneration:

```php
$store = WC_Data_Store::load( 'customer-download' );
$store->delete_by_order_id( $order->get_id() ); // Also removes related download logs.
$order->get_data_store()->set_download_permissions_granted( $order, false );
wc_downloadable_product_permissions( $order->get_id(), true );
```

Regeneration resets counters and expiry. Require an authorized admin action, log it, and never perform it on ordinary page loads.

## Read access at the correct level

```php
$available = wc_get_customer_available_downloads( $customer_id );
```

This is preferable to querying the table: it excludes exhausted/expired permissions, verifies the order's current download state, confirms that the product and stable file ID still exist, and rejects disabled files.

For diagnostics, `wc_get_customer_download_permissions()` returns permission objects filtered by the data store, but it is not the final display/access decision. Do not reconstruct download URLs from product meta.

## Secure the delivery boundary

Woo download links contain the order key, customer email (or hash), product ID, and download ID. They are bearer-like secrets, not WordPress nonces. Do not put them in public caches, analytics URLs, support screenshots, or logs.

- Prefer the Woo protected uploads directory or private object storage with controlled delivery.
- A Media Library/public object URL can bypass Woo's handler, limits, expiry, login requirement, and logs.
- Approved directories are a path allowlist, not web-server access control.
- Prefer `force` for local protected files, or correctly configured `xsendfile`/`X-Accel-Redirect` for scale.
- `redirect` exposes the source URL; use it only when the target is independently protected or intentionally public.
- The login-required setting only adds an ownership check when the permission has a nonzero user ID. Guest-order links remain bearer links.
- Never replace `woocommerce_download_product_filepath` from untrusted request input.

## Preserve lifecycle invariants

- Product variation permissions use the variation ID.
- `downloads_remaining = ''` means unlimited; do not coerce it to zero.
- Expiry is calculated from completion date when available, otherwise from grant time.
- Order deletion through Woo CRUD cleans permissions and logs; direct SQL can orphan data.
- Fully refunded line items are excluded from order downloadable items.
- Creating a customer account can reassign and regenerate guest-order permissions.
- Range requests may be counted later through Action Scheduler to avoid charging one segmented download multiple times.

## Audit checklist

1. Confirm file URLs cannot bypass Woo authorization.
2. Confirm download IDs remain stable unless revocation/migration is intended.
3. Confirm grants are idempotent and not duplicated by retries or forced calls.
4. Confirm the related order still controls access.
5. Test exhausted, expired, guest, logged-in wrong-user, refunded, removed-file, and disabled-file cases.
6. Test actual server delivery for `force`, `xsendfile`, or redirect; PHP-only tests cannot validate web-server protection.
7. Treat download URLs, order keys, customer email, and logs as sensitive data.

## Cross-references

- Use `wc-order-lifecycle-and-items` for order status, payment, refund, and item behavior.
- Use `wc-action-scheduler-jobs` for partial-download tracking or background reconciliation.

## References

- Verified source paths:
  - `wp-content/plugins/woocommerce/includes/abstracts/abstract-wc-product.php`
  - `wp-content/plugins/woocommerce/includes/class-wc-product-download.php`
  - `wp-content/plugins/woocommerce/includes/class-wc-customer-download.php`
  - `wp-content/plugins/woocommerce/includes/class-wc-download-handler.php`
  - `wp-content/plugins/woocommerce/includes/wc-order-functions.php`
  - `wp-content/plugins/woocommerce/includes/wc-user-functions.php`
  - `wp-content/plugins/woocommerce/includes/data-stores/class-wc-customer-download-data-store.php`
  - `wp-content/plugins/woocommerce/src/Internal/ProductDownloads/`
  - `wp-content/plugins/woocommerce/src/Internal/RestApi/Routes/V4/Orders/ActionController.php`

Files in this skill

  • SKILL.md9.2 KB
  • agents/openai.yaml239 B
  • references/core-download-contract.md11 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in security →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.