Downloadable products
CartThrob protects the delivery route and validates the source of a downloadable file. Your template still decides who is eligible to see or submit a download control—for example, by rendering it only inside an Order Item, Purchased Item, member, or submitted-order result that the customer is authorized to access.
CartThrob 10 separates two questions:
- May this field supply a download?
- Is this particular managed file, local path, or URL an allowed source?
Both checks must pass. Encryption or an opaque form does not make an unsafe source valid.
Recommended setup
For a new product download:
- Add an ExpressionEngine File field to the Product channel when the file belongs in File Manager. This is the preferred option, including cloud-backed upload destinations.
- Use a CartThrob Download field when the source must be a validated managed-file reference, an approved raw server path, or an approved HTTP(S) URL entered as one value.
- Map or reference that field from the appropriate product/template.
- Render
download_file_formorget_download_linkonly inside the site’s purchase/member authorization logic. - Test the download as the intended customer and as an unauthorized visitor.
ExpressionEngine File and CartThrob Download fields are automatically approved field types. Older Text or other custom fields require an exact cartthrob_download_fields entry for their channel and field ID.
See CartThrob Download fieldtype for field setup and validation behavior.
Source rules
| Source | Strict-mode rule |
|---|---|
| ExpressionEngine managed file | Allowed automatically when the reference resolves. Adapter-backed and cloud storage stay on ExpressionEngine’s managed delivery path. |
| CartThrob managed-file reference | Allowed when the reference resolves. |
| Raw absolute local path | The file must exist, be readable, and resolve inside a directory in cartthrob_download_roots. |
| Same-site public HTTP(S) URL | Allowed for the configured public site origin. |
| External HTTP(S) URL | Its exact origin must be in cartthrob_download_origins. Redirects are checked too. |
| Exact local-development site URL | Requires cartthrob_download_allow_local_site_url; use only for local development. |
| Relative path, stream wrapper, URL credentials, traversal, malformed URL, or private/reserved network destination | Rejected. An allowlist does not override these checks. |
For direct file= and free_file= tag parameters, field approval does not apply, but the source rules still do.
Configuration
Put strict download settings in system/user/config/config.php:
The Configuration Overrides reference documents every durable v10 override, including MSM forms and security constraints.
$config['cartthrob_download_fields'] = [
7 => [42, 43],
];
$config['cartthrob_download_roots'] = [
'/srv/private-downloads',
];
$config['cartthrob_download_origins'] = [
'https://downloads.example.com',
];
// Local development only; leave false or omit in production.
$config['cartthrob_download_allow_local_site_url'] = false;
Use the narrowest practical local directory. Origins are exact scheme/host/port combinations, not wildcards or URL prefixes.
For MSM, use explicit site maps where required. Version Review displays the effective configuration for the current site.
Choosing the output
Form-based download
download_file_form creates a POST form. The customer does not receive a reusable link, making it the preferred choice when a button inside an authenticated account/order page is sufficient.
Link-based download
get_download_link creates an encrypted CartThrob action URL. It can be convenient in email or account views, but the URL may be copied. Bind it to the intended member or role when it should not be transferable.
Validation is not authorization
Source validation prevents CartThrob from reading or requesting an unintended location. It does not prove that the current customer purchased the product.
Keep download controls inside a verified purchase/member context, such as:
{exp:cartthrob:order_items member_id="CURRENT_USER"}
{exp:cartthrob:download_file_form
field="product_download"
entry_id="{item:entry_id}"
member_id="CURRENT_USER"}
<button type="submit">Download {title}</button>
{/exp:cartthrob:download_file_form}
{/exp:cartthrob:order_items}
For a guest checkout result, submitted_order_info can control whether the download form is initially rendered, but there is no member ID to bind. Treat the resulting control as bearer access: use it only when that is acceptable, or add a site-specific order entitlement/token workflow.
Older direct-download templates
CartThrob continues to recognize the older view_download_link and download_file pair so existing stores can migrate. view_download_link encrypts a literal file source into a URL for a template that calls download_file; download_file then validates and streams that source. Version Review inventories both tags.
An older implementation may look like this:
<a href="{exp:cartthrob:view_download_link
template='{path=downloads/file}'
file='{product_download}'
member_id='{logged_in_member_id}'}">Download</a>
The target downloads/file template then decodes URL segments and must place the streaming tag before any output:
{exp:cartthrob:download_file
encrypted="yes"
file="{segment_3}"
member_id="{segment_4}"}
For new work, replace the two-template route with a validated product field and one native form inside the authorized purchase loop:
{exp:cartthrob:download_file_form
field="product_download"
entry_id="{entry_id}"
member_id="CURRENT_USER"}
<button type="submit">Download</button>
{/exp:cartthrob:download_file_form}
The older tags are not a way around CartThrob 10 validation. Their literal or decoded source must still pass the managed-file, local-root, or remote-origin policy. view_download_link does not resolve field= plus entry_id=; migrate to one of the current tags for field binding.
Upgrading an existing catalog
On an upgraded site, open CartThrob → Version Review → Downloadable Products:
- Run Tag Review to inventory download tags and their fields/sources.
- Run Field Review to validate every non-empty value in custom fields listed by
cartthrob_download_fields. - Prefer moving old Text-field values into an ExpressionEngine File or CartThrob Download field.
- If migration is not practical, approve only the exact custom fields, roots, and external origins required.
- Test purchased and unauthorized downloads.
- Disable or remove
cartthrob_legacy_download_sources, rerun both reviews, and test again.
See Version Review and compatibility messages for every result and its resolution.
CartThrob does not add download-count or time limits. If those are required, implement them in the surrounding authorization workflow.