Documentation
Install, configure, and use APO FJ Downloads on Joomla 4, 5, and 6.
1. Installation
APO FJ Downloads ships as a single installable Joomla package that contains every sub-extension (component, plugins, modules) needed for the integration.
- Download your package from https://apotentia.com/account/downloads (Pro customers) or the public Lite download page.
- In your Joomla admin, go to System → Install → Extensions → Upload Package File.
- Select the
pkg_apofjdownloads-1.0.0.zip(orpkg_apofjdownloads_lite-1.0.0.zip) file and upload. - Wait for the installer to finish — it installs the component, the content plugin, the SEF router system plugin, and the four companion modules in one pass.
fileinfo PHP extension (used for MIME-type verification on every upload).
2. License activation
If you bought a Pro tier (Single Site, Agency, or Unlimited), you'll have received an email with your license key — looks like FJDL-XXXX-XXXX-XXXX-XXXX.
- In the Joomla admin, go to Components → APO FJ Downloads → Settings.
- Find the License key field and paste your key.
- Click Save & Close.
- Verify activation: the Settings page shows the license status (active, expired, or invalid) and the number of remaining domain activations.
The activation reaches out to https://apotentia.com/api/index.php?route=activate to register the domain. If the call fails (firewall, DNS, etc.) the extension keeps working in Lite mode until activation succeeds.
3. Creating categories
Categories are the top-level organization for your downloads. They support nesting (a category can have sub-categories), per-category access groups, and per-category description / image.
- Go to Components → APO FJ Downloads → Categories → New.
- Set a title, alias (auto-generated), parent category (or "no parent" for top-level), description, and image.
- Under Permissions, choose which Joomla viewing access groups can see this category. Default is Public.
- Save and close. The category is now available when you create download items.
4. Adding download items + files
A "download item" is the unit your visitors interact with — title, description, screenshots, and one or more files attached. Files can be uploaded directly or pulled from a folder you've designated.
- Components → APO FJ Downloads → Downloads → New
- Fill in title, alias, category, short description, and full description (HTML editor with image support).
- Under Files, click Upload to attach one or more files. Each upload runs through MIME-type verification: only allowed types (110+ formats listed in the admin) are accepted; everything else is rejected at upload time.
- Optional: set a license tag, version number, file size override, and access group (overrides the category default).
- Save. Visitors at
/index.php?option=com_apofjdownloads&view=downloads(or your SEF URL) now see the item in its category.
Both Lite and Pro honor the MIME-type allowlist and serve files exclusively through PHP controllers, never as direct filesystem URLs. Files in your /files directory cannot be hotlinked, brute-forced by guessing names, or scraped. Lite enforces a 5 MB-per-file size cap; Pro removes that cap.
5. Access control
APO FJ Downloads layers on Joomla's existing User Groups + Viewing Access Levels, so your existing site permissions just work. The control points are:
- Per-category access: set a viewing access level on each category. Members of groups outside that level don't see the category at all.
- Per-download access: overrides category-level. Useful for a "members-only" download inside a public category.
- Per-file access: Pro feature. Multiple files attached to one download item can each have their own access level — e.g. a free preview and a paid full version on the same item.
- Token-based serving: when a visitor clicks a download, the controller issues a one-time signed token, validates it server-side on the file-serve request, increments the download counter, logs the event, and serves the bytes. The token expires within seconds and is single-use.
6. Content shortcodes (Pro)
Pro ships a content plugin that lets you embed download items inline in articles, modules, or any HTML field that runs Joomla's content plugins.
Embed a single download:
{apofjdl id="42"}
Embed a download list filtered by category:
{apofjdl category="reports" limit="10" sort="latest"}
Available attributes: id, category, limit, sort (latest, popular, alphabetical), layout (card, list, table), access (override of viewer access).
7. Companion modules (Pro)
Pro installs four companion modules ready to drop into any module position:
- Latest Downloads — most recently published items, configurable count + category filter.
- Most Popular — by all-time download count.
- Categories Tree — nested category navigation, optionally hiding empty categories.
- Featured Downloads — pulls items flagged as Featured in their admin record.
Each module has its own layout file under modules/mod_apofjdl_*/tmpl/ that you can override via Joomla's standard template-override mechanism.
8. Layout customization
APO FJ Downloads uses a Twig-based layout engine with a 5-level cascade: alias → download → category → global → override → system. The most-specific layout wins.
To customize the look of the download list, item detail page, or shortcode output:
- Go to Components → APO FJ Downloads → Layouts.
- Either edit one of the six default layouts (card, list, table, detailed, compact, minimal) or create a new one.
- Twig syntax:
{{ download.title }},{% for file in download.files %}, etc. Full template variable list is on the Layouts admin page. - Assign your layout at the level you want it to apply (one specific download, a category, or globally).
9. Updates and renewals
Pro is a one-time purchase: you get a perpetual license + 1 year of updates. After the year, the extension keeps working forever — only updates stop.
- To check for updates: Joomla's System → Manage → Update shows new versions when your update entitlement is active.
- To renew updates: log into your account portal, find the FJ Downloads update subscription, click Renew. Pay $49/$99/$199 again for another 365 days of updates.
- To skip a year and renew later: also fine. Skipped time isn't credited, but the extension continues to work and you can renew whenever.
10. Settings reference
The full settings panel lives at Components → APO FJ Downloads → Settings. Options are grouped into tabs.
License
- License key — paste the key from your purchase email. Lite leaves this blank.
- License status — read-only display of current state (active, expired, invalid, or unactivated). Updated each time the extension reaches out to
apotentia.com/api/.... - Activations — read-only count of domains your key is active on, with a per-domain deactivate button if you need to free a slot.
Uploads
- Maximum file size — Lite is hard-capped at 5 MB regardless of this setting. Pro respects whichever is lower of this value and your PHP
upload_max_filesize/post_max_size. - Allowed MIME types — comma-separated allowlist. Defaults to ~110 common types (PDF, Office documents, audio, video, archives, images, source-code archives, plain text). Add custom types here when you need to allow something the default list excludes.
- Antivirus scan (ClamAV) — optional. Provide the absolute path to
clamscan; if set, every uploaded file is scanned before being accepted. Files that trigger a detection are quarantined automatically. - Storage path — directory where uploaded files are stored. Defaults to
JPATH_ROOT/files. Must be writable by the web server but not directly served (the bundled.htaccess+ PHP file controller enforce this).
Tokens & file serving
- Token TTL — how long a one-time download token stays valid after the click that issued it (default: 60 seconds). Higher values are more forgiving on slow connections; lower values are more secure against forwarded URLs.
- Token signing key — auto-generated on install, stored in the database. Rotates token validity for any in-flight downloads if you regenerate.
- Single-use enforcement — when on (default), each token can be redeemed exactly once. Off allows resumed downloads to retry but weakens the security model.
Rate limits
- Per-IP downloads per hour — default 60. Visitors over this in a rolling hour get HTTP 429.
- Per-user downloads per hour — default 120 (counted only for authenticated users; anonymous users hit the per-IP limit).
- Per-IP login attempts — separate from downloads, applies to admin panel access.
- Block duration — how long a visitor is blocked after exceeding a limit (default: 5 minutes).
Download quotas
- Daily quota per user — total bytes a single user can download in 24 hours. Zero disables the quota.
- Monthly quota per user — same, rolling 30 days.
- Per-download max retries — how many times a visitor can re-request the same file (default: 5).
Logging
- Log retention (days) — how long the download log keeps records (default: 90). Older entries are pruned by the daily cleanup job.
- IP hashing — when on (default), IPs are hashed with a per-install salt before storage. The admin log viewer shows the hash, never the raw address. Designed for GDPR-compliant download logging.
- What's logged — timestamp, hashed IP, user (if authenticated), download id, file id, status (success/failed/blocked), and rejection reason if applicable.
Layouts
- Default global layout — falls back to this when no per-category or per-download layout is set.
- Layout cascade — alias-specific overrides → per-download → per-category → global default → system default. Highest specificity wins.
11. License API reference
The license API at https://apotentia.com/api/ handles activation, validation, and status calls. Used by the extension itself for self-checks; available for direct integration when you're building tooling around your installs.
Base URL (with the ?route= query-string fallback that works across all hosting setups):
https://apotentia.com/api/index.php?route=<endpoint>
All POST endpoints accept JSON request bodies and return JSON responses. Every response is HMAC-signed; the signature is included in the response payload so clients can verify the response wasn't tampered with in transit.
POST /validate
Check whether a license key is currently valid. Read-only (no side effects).
curl -X POST https://apotentia.com/api/index.php?route=validate \
-H "Content-Type: application/json" \
-d '{"license_key":"FJDL-XXXX-XXXX-XXXX-XXXX"}'
Response (200):
{
"valid": true,
"license_key": "FJDL-XXXX-XXXX-XXXX-XXXX",
"product": { "id": 7, "slug": "apo-fjdownloads", "name": "APO FJ Downloads" },
"status": "active",
"max_activations": 5,
"current_activations": 2,
"activations_remaining": 3,
"expires_at": null,
"is_perpetual": true,
"days_remaining": null
}
Errors: 403 with error_code in {INVALID_KEY, LICENSE_EXPIRED, LICENSE_SUSPENDED, LICENSE_REVOKED, PRODUCT_MISMATCH}.
POST /activate
Register the license on a specific machine (Joomla site). Increments current_activations by 1. Idempotent: a second call from the same machine_id updates the heartbeat instead of consuming another slot.
curl -X POST https://apotentia.com/api/index.php?route=activate \
-H "Content-Type: application/json" \
-d '{
"license_key": "FJDL-XXXX-XXXX-XXXX-XXXX",
"machine_id": "<sha256 of site URL + db host>",
"machine_name": "example.com",
"os_info": "Joomla 5.1 / PHP 8.2 / Linux"
}'
Response (200):
{
"success": true,
"message": "License activated successfully",
"activation_id": "53",
"activations_remaining": 2
}
Errors: 403 if license invalid, 409 if no activation slots remain.
POST /deactivate
Free an activation slot. Use this when retiring a site so the slot can be reused on a new domain.
curl -X POST https://apotentia.com/api/index.php?route=deactivate \
-H "Content-Type: application/json" \
-d '{
"license_key": "FJDL-XXXX-XXXX-XXXX-XXXX",
"machine_id": "<same as activation>"
}'
GET /info
Full license detail, including the activation list. Authenticates via the X-License-Key header (not query string), so the key is never logged in webserver access logs.
curl https://apotentia.com/api/index.php?route=info \
-H "X-License-Key: FJDL-XXXX-XXXX-XXXX-XXXX"
Response (200): license details plus an activations[] array of {machine_id, machine_name, os_info, ip_address, first_activated_at, last_seen_at}.
POST /heartbeat
Update last_seen_at on an existing activation without consuming a slot. The extension calls this in the background once per day so deactivated keys can be detected.
curl -X POST https://apotentia.com/api/index.php?route=heartbeat \
-H "Content-Type: application/json" \
-d '{"license_key":"FJDL-...","machine_id":"..."}'
POST /check-updates
Ask whether a newer version of the extension is available, scoped to the version the caller currently has installed. Returns the update channel (stable / beta) plus download URL if applicable.
curl -X POST https://apotentia.com/api/index.php?route=check-updates \
-H "Content-Type: application/json" \
-d '{
"license_key": "FJDL-XXXX-XXXX-XXXX-XXXX",
"current_version": "1.0.0"
}'
GET /health
Service health probe. No authentication. Returns {success: true, status: "healthy", database: "connected", timestamp: "..."} when the API + database are reachable; HTTP 503 otherwise.
Rate limiting
The license API is rate-limited at 100 requests per hour per IP. Exceeding this triggers a 5-minute block (HTTP 429). The extension's built-in client respects this comfortably; only an unusual integration calling the API in a loop would trip it.
12. Troubleshooting
"Upload failed: file type not allowed"
The MIME-type allowlist rejected your file. Check the file's actual MIME (Joomla detects from content, not extension). If it's a legitimate type that's blocked, add it to Settings → Allowed MIME types.
"License invalid" after pasting the key
- Double-check the key for typos or trailing spaces.
- Confirm the domain you're activating on is reachable from
apotentia.com(we ping back to verify ownership). - If you've used all your activations, deactivate one from your account portal first.
Downloads return 403 Forbidden
The visitor is logged out or in a Joomla group that doesn't have viewing access to the file's access level. Check the file's access level in the Downloads admin and the visitor's group memberships.
Downloads return 404 Not Found
Either the file was moved/deleted from /files, or the SEF router isn't enabled. Verify the file exists in the path shown in the Downloads admin record, then re-save the item to refresh the alias.
"Rate limit exceeded"
Per-visitor rate limiting is on by default (configurable in Settings). If your site has a legitimate use case for high download volume, raise the per-IP cap in Settings → Rate Limits.
13. Getting support
Pro customers get priority ticket support. To open a ticket:
- Log into your account portal.
- Go to Support tickets → New ticket.
- Select ticket type (bug, feature request, question, account issue), priority, and product.
- Submit. We'll respond by email; you can also see the thread in the portal.
For non-Pro questions or pre-sales inquiries, use the contact form.
Need help?
If anything in this guide doesn't match what you're seeing, or you've hit something not covered here, please open a support ticket — these docs improve based on what customers ask about.