A complete stock-photo and stock-video marketplace for Larapen: sell your own media with watermarked previews, unlisted YouTube embedding, queued background processing, FTP/SFTP bulk import, and secure signed-URL downloads bundled with a per-project license PDF.
Sell Photos & Videos
Publish photo and video items with per-license pricing (Personal, Commercial, Extended) or offer them for free.
Watermarked Previews
Automatically generate watermarked previews and multiple thumbnail sizes so source files stay protected until purchase.
Unlisted YouTube Embedding
Upload a short, watermarked video preview clip to YouTube as an unlisted video and embed it on the detail page.
FTP / SFTP Bulk Import
Drop files into per-category folders on an SFTP server and let the scheduler import them automatically, with optional metadata sidecars.
Queued Processing
Heavy image and video work runs on dedicated queues so the admin panel stays responsive even during large imports.
Secure Downloads
Buyers receive temporary signed-URL links. Each download ships as a ZIP containing the source file, a license PDF, and a README.
Multiple Gateways
Checkout integrates with the core payment service (Stripe, PayPal, Paddle, MoMo). Free items skip the gateway entirely.
Use Cases
Photographer Portfolio Store
You are a photographer who wants to sell licensed downloads of your work.
- Upload high-resolution sources: buyers only ever see watermarked previews until they pay.
- Offer Personal, Commercial, and Extended license tiers at different prices on the same item.
- Let customers buy without an account: a guest email is enough to receive the download link.
- Each download bundles a license PDF naming the buyer and the single project it covers.
Video Footage Marketplace
You sell stock video clips and want safe previews without exposing the full file.
- The add-on generates a short, watermarked preview clip and uploads it to YouTube as unlisted.
- The unlisted YouTube player is embedded on the detail page; the source MP4 stays private.
- Requires
ffmpeg/ffprobeon the server for video processing.
Bulk Catalog from an SFTP Drop
You have thousands of files to publish and don’t want to upload them one by one.
- Organize files into per-category folders on an SFTP server.
- Add an optional
.meta.jsonsidecar to set title, price, license, and status per file. - The scheduler scans every few minutes, enqueues an import job per new file, and processes them in the background.
- Imported files move to a
_processedfolder; permanent failures move to_failedand admins are notified.
Requirements
- Larapen CMS v1.0.0 or later
- PHP 8.3+
- MySQL 8.0+
- The PHP ZIP extension (downloads are streamed as ZIP archives)
- A running queue worker consuming the
media,video,importanddefaultqueues ffmpegandffprobeon the server: required for video items only (photo items work without them)
Http facade, so no Google SDK is required. The add-on declares no
Composer dependencies and no add-on dependencies.
Installation
Step 1: Upload the Add-on
In the admin panel, go to Admin → Add-ons and click Upload Add-on. Select the add-on’s ZIP file: the system extracts it automatically and the add-on appears in the installed add-ons list.
Step 2: Activate the Add-on
Find Stock Media in the list and click Activate. Its migrations and
permissions are set up automatically. Activation creates seven stockmedia_ tables and
registers 14 permissions.
Step 3: Start Queue Workers
Heavy media work runs on dedicated queues. Start a worker that consumes all of them:
php artisan queue:work --queue=video,media,import,default --tries=3
Step 4: Configure
Navigate to Admin → Stock Media → Settings to set the storage disk, default pricing, watermarking, FTP/SFTP import, and YouTube. See Configuration.
Step 5: Create Categories & Items
Create at least one category under Stock Media → Categories, then add your first item under Stock Media → Items → Add Item.
Purchase Code (License Key)
Stock Media is sold as a separate product, so it has its own purchase code (license key), distinct from the purchase code of the main application and from the one of every other add-on. You are asked for it when you activate Stock Media in Admin panel → Add-ons.
Our products are sold on three platforms. The way you receive a purchase code depends on where you bought the product.
| Platform / Marketplace | How you get the purchase code | Where to find it again |
|---|---|---|
| bedigit.com Store In-site purchase (Shop) |
Generated automatically when the order is paid, then sent by email, either in its own license email, or inside the order confirmation email. | My Account → My Licenses on bedigit.com |
| Gumroad | Created as soon as Gumroad notifies us of the sale, then sent in a separate email, in addition to the Gumroad receipt. | The license email, your Gumroad Library, and My Account → My Licenses on bedigit.com |
| Envato Market CodeCanyon |
Issued by Envato, not by us, and never sent by email: you download it yourself from your Envato account. | Envato account → Downloads → License certificate & purchase code |
1. bedigit.com Store (in-site purchase)
- As soon as the order’s payment status becomes Paid, a license key is generated automatically for every licensed item in the order (one key per purchased unit: buying 3 units gives 3 distinct keys).
- It is emailed to the address used on the order, either in a dedicated license email or inside the order confirmation email. Check your inbox and your spam / junk folder.
- The key stays available in your account under My Account → My Licenses. Keys are masked in the list; open the license detail page to reveal and copy the full key, see the domains it is activated on, and deactivate a domain to free an activation slot.
- The matching invoice is under My Account → My Orders.
2. Gumroad
- A Gumroad purchase produces two separate emails: the Gumroad receipt (sent by Gumroad, giving access to the files) and a license key email (sent by bedigit.com) that contains your purchase code.
- The license key email is generated as soon as Gumroad notifies us of the sale, so it normally arrives within seconds of the payment. Here too, check your inbox and your spam / junk folder.
- When the Gumroad product uses Gumroad’s own license-key feature, the same key also appears in your Gumroad receipt and under Library → your purchase on gumroad.com.
- Use the same email address on bedigit.com as on Gumroad: your keys are then linked to your account automatically and listed under My Account → My Licenses, even if you register after the purchase. You can also add a Gumroad key manually from My Account → My Gumroad Licenses.
3. Envato Market (CodeCanyon)
- Envato purchase codes are issued and delivered by Envato Market, never emailed by us, so there is nothing to look for in your spam folder: you retrieve the code from your Envato account.
- Log in to your Envato / CodeCanyon account, open the Downloads page, find the item, and choose License certificate & purchase code from the Download dropdown. The code is written in that certificate.
- An Envato purchase code looks like
12345678-90ab-cdef-1234-567890abcdef(8-4-4-4-12 characters). It never changes, and renewing item support does not issue a new one. - Official Envato article: Where Is My Purchase Code?
Configuration
Settings are managed on a single tabbed page at Admin → Stock Media → Settings
(stored in the settings table with the stockmedia_ prefix). Config file defaults
live in config/stockmedia.php. The page has four tabs (General, Watermark, FTP/SFTP, and
YouTube) plus a Page Header tab for front-end header overrides.
General Settings
| Setting | Description | Default |
|---|---|---|
stockmedia_storage_disk |
Filesystem disk used to store original source files (e.g. local, s3). |
local |
stockmedia_default_price |
Default price suggested when creating a new item. | 0 |
stockmedia_default_currency |
Default 3-letter currency code for items without an explicit currency. | USD |
stockmedia_default_license_type |
Default license tier: personal, commercial, or extended. |
personal |
stockmedia_max_downloads |
Maximum downloads allowed per purchased item. Set to 0 for unlimited. | 5 |
stockmedia_download_expiry_days |
Days a download link stays valid after purchase. Set to 0 to never expire. | 7 |
stockmedia_free_require_email |
Require an email address when claiming a free item (otherwise it is optional). | true |
stockmedia_payment_terms_override_enabled |
Override the global sale terms (Settings › General › Sale Terms) for the paid checkout. When off, the global settings apply. When not required, the stock media checkout keeps its generic terms checkbox. | false |
stockmedia_payment_terms_enabled |
While overriding: require buyers to accept the sale terms before paying. | global value |
stockmedia_payment_terms_page_id |
While overriding: the published page whose content is shown in the terms accordion. | global value |
stockmedia_image_quality |
JPEG/WebP quality (1–100) for generated previews and thumbnails. | 85 |
stockmedia_related_items_count |
Number of similar items (2–6) shown in the “You may also like” block of a media detail page. Items from the same category and of the same media type come first; its “See more like this” link opens that filtered listing. | 5 |
Watermark Settings
Watermarking is configured separately for photos and videos. Upload your watermark PNG (with transparency) to the core Media library first, then paste its Media ID into the appropriate field.
| Setting | Description | Default |
|---|---|---|
stockmedia_watermark_photo_enabled / stockmedia_watermark_video_enabled |
Master toggle for photo / video watermarking. | off |
stockmedia_watermark_photo_image_path / ..._video_image_path |
The Media library reference for the watermark overlay image. | : |
stockmedia_watermark_photo_position / ..._video_position |
One of top-left, top-right, center, bottom-left, bottom-right, tile. |
bottom-right |
stockmedia_watermark_photo_opacity / ..._video_opacity |
Overlay opacity, 0–100. | 60 |
stockmedia_watermark_photo_scale / ..._video_scale |
Overlay size as a percentage of the media width, 1–80. | 20 |
stockmedia_watermark_video_clip_seconds |
Length (seconds) of the generated video preview clip, 3–60. | 15 |
stockmedia_ffmpeg_binary / stockmedia_ffprobe_binary |
Paths to the ffmpeg / ffprobe binaries (used for video processing). |
system default |
FTP / SFTP Import Settings
| Setting | Description | Default |
|---|---|---|
stockmedia_ingest_enabled | Master toggle for the FTP/SFTP import pipeline. | off |
stockmedia_ingest_driver | Connection driver: ftp or sftp. | sftp |
stockmedia_ingest_host | Server hostname. | : |
stockmedia_ingest_port | Server port. | 22 (sftp) / 21 (ftp) |
stockmedia_ingest_username / stockmedia_ingest_password | Connection credentials. | : |
stockmedia_ingest_root | Root path scanned on the remote server. | / |
stockmedia_ingest_ssl | Use SSL for the FTP driver. | off |
stockmedia_ingest_delete_after_import | Delete (rather than move to _processed) after a successful import. | off |
stockmedia_ingest_timeout | Connection timeout in seconds, 1–300. | 30 |
YouTube Settings
| Setting | Description |
|---|---|
stockmedia_youtube_enabled | Master toggle for uploading video preview clips to YouTube. |
stockmedia_youtube_client_id / stockmedia_youtube_client_secret | OAuth 2.0 credentials from a Google Cloud project with the YouTube Data API v3 enabled. |
After saving your client credentials, click Connect to YouTube and grant the requested scope. The refresh token is stored encrypted in the settings table.
Admin: Items
Items are the photos and videos you sell. Managed via Stock Media → Items.
Creating an Item
The item form (Items → Add Item) collects:
- Type: photo or video.
- Title, description, alt text, keywords (translatable).
- Category: links the item to a core category.
- Source file: the original high-resolution file, stored on the configured storage disk.
- Per-license pricing: a price for each offered tier (Personal, Commercial, Extended). A tier with no price set is simply not offered. A tier priced at 0 is offered for free.
- Currency and SEO title/description (translatable).
- Status:
draft,published, orarchived.
Processing & Status
When an item is created or its source changes, the add-on dispatches a background job
(ProcessPhotoJob or ProcessVideoJob) that generates the watermarked preview,
thumbnails (thumbnail / medium / large), and, for video, a watermarked preview clip uploaded to
YouTube. The item carries a processing status badge:
pending→ queued, not yet processedprocessing→ a worker is generating previewsready→ previews generated; the item can be published and boughtfailed→ processing error (shown in admin notifications; the item carries aprocessing_errormessage)
published_at date, if set, is in the past).
Reprocessing
If processing fails (for example because ffmpeg was missing), use the Reprocess
action on the item to re-dispatch its jobs. The stockmedia.items.reprocess permission controls
this action.
Admin: Categories
Categories organize your catalog and drive the front-end category pages. Managed via
Stock Media → Categories. They reuse the core categories table with the
stockmedia_item categorizable type.
- Standard CRUD: create, edit, delete.
- Reorder: a dedicated drag-and-drop reorder screen controls the display order of categories.
- Categories are also used as the folder names for the FTP/SFTP import (see FTP Import).
Admin: Settings
All add-on options live on the single tabbed Settings page. See the Configuration section for the full reference of every tab and setting: General, Watermark, FTP/SFTP, and YouTube.
Admin: FTP / SFTP Import
The import pipeline lets you publish files in bulk from an SFTP (or FTP) server, organized by category slug. Managed via Stock Media → Import.
Folder Layout
Create one folder per category slug under the configured root. Each file may have an optional
.meta.json sidecar that overrides title, price, license, and other fields:
/<root>/
/sunsets/
photo-001.jpg
photo-001.jpg.meta.json # optional
/city-life/
skyline.mp4
skyline.mp4.meta.json
{
"title": { "en": "Sunset over Paris" },
"keywords": ["sunset", "paris"],
"is_free": false,
"price": 9.99,
"currency": "USD",
"license_type": "commercial",
"status": "published"
}
How Import Runs
- The scheduler runs
stockmedia:importevery 5 minutes by default (configurable inconfig/stockmedia.php). - Each new file becomes a row in
stockmedia_import_jobsand is processed in the background. - Allowed image types:
jpg, jpeg, png, webp, avif. Allowed video types:mp4, mov, mkv, webm. - Successful files move to
<root>/_processed/…; permanent failures move to<root>/_failed/…and admins receive a notification. - You can also trigger a run manually from the Import screen, or via the CLI:
php artisan stockmedia:import --limit=50
php artisan stockmedia:import --category=sunsets
Import Jobs Table
The Import screen lists recent import jobs with their category, detected type, status badge
(queued, processing, imported, failed, skipped),
and any error message.
Admin: Purchases
Every checkout (paid or free) creates a purchase record. Managed via Stock Media → Purchases.
- List: purchase number, buyer email/name, total, currency, status badge, and date.
- Detail: the purchased item(s), license tier, payment method and reference, transactions, and download counters.
- Refund: an admin with the
stockmedia.orders.refundpermission can refund a paid purchase, which sets its status torefundedand revokes download access.
Purchase status values are pending, paid, failed, refunded,
and expired. Purchase numbers follow the pattern SM-YYYY-000001.
Admin: Reports
The Sales report (Stock Media → Reports) summarizes revenue and sales volume over time.
Requires the stockmedia.reports.view permission.
Dashboard Widgets
The add-on also contributes three widgets to the core admin dashboard:
- Recent Sales: the latest completed purchases.
- Top Items: best-selling items by purchase count.
- Processing Queue: items currently pending or processing.
Front-end: Browse & Detail
Browse
The public catalog lives at /stock (and /{locale}/stock). Visitors can browse all
published items and filter by category at /stock/category/{slug}. Each card shows the
watermarked preview, title, and price.
Item Detail
The detail page at /stock/{slug} shows:
- The watermarked preview (photo) or the embedded unlisted YouTube player (video).
- Title, description, dimensions, aspect ratio, and, for video, duration and framerate.
- The offered license tiers and their prices, with a per-tier coverage breakdown (worldwide, perpetual, single-project, commercial use, resale, etc.).
- A Buy now (or Get for free) call to action.
Front-end: Buy & Checkout
Paid Items
- The buyer picks a license tier and clicks Buy now, landing on
/stock/checkout/{slug}. - They provide an email, optional name, and a project name (licenses are per-project), then choose a payment method.
- The add-on creates a pending purchase and hands off to the selected gateway (Stripe, PayPal, Paddle, or MoMo).
- When the gateway confirms payment, the purchase is marked paid and the buyer is sent a signed download link by email.
Free Items
Items whose selected tier is priced at 0 use the free flow at /stock/free/{slug}. The buyer
still provides a project name (and an email if stockmedia_free_require_email is on). The purchase
is created and immediately marked paid with total = 0, no gateway is involved, and the
download link is dispatched.
Front-end: Download & Licenses
Downloads are served from a single signed route: /stock/download/{purchase}/{item}.
- The link in the buyer’s email is a temporary signed URL that expires after
stockmedia_download_expiry_daysdays (0 = effectively never). - Logged-in owners can also download directly from their account without a signed link (ownership is checked).
- Each item can be downloaded up to
stockmedia_max_downloadstimes (0 = unlimited). Exceeding the limit, an expired purchase, or a non-paid purchase returns an error. - Every download is recorded in an audit table (IP, user agent, bytes served).
What’s in the ZIP
The download is streamed as a ZIP archive containing:
- The original source file.
- A license PDF certificate naming the buyer, the project, and the license tier clauses.
- A short README.txt for non-technical buyers.
License Tiers
| Tier | Summary |
|---|---|
| Personal | Worldwide, perpetual, single project, personal media use. No commercial use, no resale. |
| Commercial | Adds commercial use across all digital and print media. No resale. |
| Extended | Adds resale within end products (the asset may not be redistributed on its own). |
Front-end: My Downloads
Authenticated buyers get a My downloads entry in their account menu, linking to
/account/stockmedia/purchases. It lists their purchases and links each one to a detail page
(/account/stockmedia/purchases/{number}) where they can re-download purchased items (subject to
the download limit and expiry).
Notifications
The add-on registers five notification types with the central notification system (Admin → Settings → Notifications):
| Notification | Recipient | Trigger | Channels |
|---|---|---|---|
SendDownloadLinkNotification |
Buyer | Purchase completed (paid) | |
NewSaleAdminNotification |
Admins | Purchase completed (paid) | mail, database |
ItemProcessedNotification |
Admins | An item finished background processing | database |
FtpImportFailedNotification |
Admins | An FTP/SFTP import permanently failed | mail, database |
YoutubeUploadFailedNotification |
Admins | A YouTube preview upload failed | mail, database |
Updating
There are two ways to update this add-on: via the admin panel (recommended) or by replacing files manually.
Method 1: Admin Panel Upload (Recommended)
- Download the latest
.zipfile of this add-on. - Go to Admin panel → Add-ons and click Upload.
- Select or drag the
.zipfile into the upload area. - A confirmation prompt shows the current and new version numbers. Click Replace to proceed.
- Go to Admin panel → System Update (
/admin/update) to apply any pending database migrations.
Method 2: Manual File Replacement
- Replace the
extensions/addons/stockmediadirectory with the new version. - Run
php artisan migrateto apply new migrations. - Clear caches:
php artisan config:clear && php artisan view:clear && php artisan route:clear. - Restart your queue workers so they pick up new job code.
Uninstallation
Switching an add-on off without losing anything is a deactivation: go to Admin panel → Add-ons, find Stock Media and click Deactivate.
- Its routes, views, admin menu entries and permissions stop being registered, and its front-end pages stop answering.
- Its database tables and all the data they hold are kept, and its files stay under
extensions/addons/stockmedia/. Nothing is deleted. - The purchase code recorded at activation is kept too, so activating the add-on again does not ask for it.
- Deactivation is refused while another active add-on depends on this one: deactivate that add-on first.
Click Activate on the same card to switch it back on. Pending migrations are re-run, assets are republished, and the add-on picks up exactly where it left off.
Removing
Removing is permanent and destroys the add-on's data. The Remove button only appears on a deactivated add-on, so removal is always two steps:
- Deactivate Stock Media (see Uninstallation).
- Click Remove on its card and confirm the prompt.
The admin panel then, in one pass:
- runs the add-on's uninstall hook, if it ships one, while its code is still on disk;
- revokes the permissions declared in its
addon.json; - rolls back its migrations (this drops its database tables and every row they hold) and purges its entries from the
migrationstable, so a later reinstall migrates from scratch; - deletes its published assets:
public/addons/stockmedia/,public/vendor/stockmedia/andstorage/app/public/addons/stockmedia/; - deletes the add-on directory
extensions/addons/stockmedia/; - deletes its row in the
addonstable (the recorded purchase code goes with it) and clears the application cache.
Removal is refused, with an explanatory message and before anything is destroyed, when the add-on is still active, when another active add-on depends on it, or when the web server (PHP) user cannot delete extensions/addons/stockmedia/. In that last case, give that user write permission on the directory and on its parent, then try again.
Deleting the folder over FTP or SSH is not equivalent: the add-on's tables, its entries in the migrations table and its addons row are all left behind, and its card stays in the list. Use Remove in the admin panel instead.
Troubleshooting
Item stuck on “pending” or “processing”
- Confirm a queue worker is running and consuming the
media,videoandimportqueues. - Check the
failed_jobstable andstorage/logs/laravel.logfor job errors. - Use the Reprocess action (or
php artisan stockmedia:reprocess {id}) to re-dispatch.
Video items fail processing
- Video processing requires
ffmpegandffprobe. Install them on the server. - Use the Test ffmpeg button on the Watermark settings tab to confirm Larapen can call the binary.
- If the YouTube upload fails, the item still processes but the YouTube preview is marked failed and admins are notified.
Item not visible on the front-end
- The item must be published and have processing status ready.
- If a
published_atdate is set, it must be in the past. - At least one license tier must have a price set, otherwise the item offers nothing to buy.
FTP / SFTP import does nothing
- Confirm
stockmedia_ingest_enabledis on and the host is set, then use Test connection. - Files must live in a folder named after an existing category slug.
- Run
php artisan stockmedia:importmanually to see queued counts; check that the scheduler is running.
Buyer didn’t receive the download link
- Verify your mail configuration in
.env(SMTP, Mailgun, etc.). - Ensure a worker is consuming the
defaultqueue (notifications are queued). - Check that the gateway webhook actually fired and the purchase is marked
paid.
Download link returns an error
- 410 Gone: the per-item download limit was reached, the link expired, or the purchase is no longer paid.
- 403: an unsigned link was used by a non-owner. Use the signed link from the email, or log in as the buyer.
- Ensure the PHP ZIP extension is installed and
storage/appis writable.
Stock Media v1.0.0: Part of the Larapen CMS platform.
© BeDigit. All rights reserved.