Classified Ads
A full-featured classified ads marketplace for your Larapen site. Users can create, browse, search, and manage listings with custom fields, location filtering, image galleries, and built-in messaging: all with admin moderation and granular permissions.
Custom Fields
Define unlimited custom fields per category or globally. Support for 10 field types including text, select, checkbox, date, and price range.
Moderation System
Optional admin approval workflow. Approve, reject (with reason), or auto-publish listings. Configurable per-site.
Search & Filtering
Full-text search across titles, descriptions, and locations. Filter by category, price range, condition, city, and custom fields.
Buyer-Seller Messaging
Built-in contact form with email notifications. Messages tracked per listing with read/unread status.
Favorites & Saved
Authenticated users can bookmark listings for later. Toggle save/unsave with a single click.
Multi-Language
All listing content, categories, and custom fields are translatable via spatie/laravel-translatable. Full i18n support.
Auto-Expiration
Listings expire after a configurable number of days. Expired listings are automatically hidden from public view.
Community Reporting
Users can report inappropriate listings. Admins review, dismiss, or act on reports from a dedicated panel.
Use Cases
General Marketplace
Run a Craigslist-style classified ads board where users post items for sale, services, or rentals. Categories and custom fields let you structure any type of listing.
- Enable user registration and listing creation.
- Define categories (Vehicles, Electronics, Real Estate, Jobs, etc.) with category-specific custom fields.
- Enable moderation to review submissions before they go live.
- Buyers message sellers directly through the built-in contact form.
Business Directory
Create a directory of local businesses with custom fields for opening hours, services offered, and contact details.
- Disable the price field or make it optional.
- Add custom fields like “Opening Hours”, “Website URL”, and “Services Offered”.
- Use location fields (city, region, address) for geographic filtering.
Internal Asset Board
A private board for an organization to list and exchange internal assets, equipment, or resources.
- Disable guest browsing: require login to view listings.
- Disable public publishing: only admins create listings.
- Use the “condition” field (new, used, refurbished) for asset tracking.
Requirements
| Requirement | Details |
|---|---|
| Larapen Core | ≥ 1.0.0 |
| PHP | ≥ 8.2 |
| MySQL | ≥ 8.0 |
| User Accounts | Required: the add-on depends on the core users table for listing ownership, messaging, favorites, and reporting. |
| Dependencies | None: no other add-ons required. |
Installation
Step 1: Upload Add-on
Place the classified folder inside the addons/ directory of your Larapen installation:
Step 2: Activate
Go to Admin → Add-ons → Installed Add-ons and activate Classified Ads.
Step 3: Run Migrations
This creates six tables: classified_listings, classified_custom_fields,
classified_custom_field_values, classified_messages,
classified_reports, and classified_saved_listings.
Step 4: Clear Caches
Step 5: Configure
Navigate to Admin → Classified Ads → Settings to configure moderation, publishing rules, image limits, listing duration, and notification preferences.
Configuration
Configuration defaults are defined in config/classified.php and can be overridden via admin settings (stored in the database with the classified_ prefix).
| Setting Key | Type | Default | Description |
|---|---|---|---|
per_page |
int | 12 | Listings per page on the front-end |
admin_per_page |
int | 15 | Listings per page in the admin panel |
max_images |
int | 5 | Maximum images per listing (featured + gallery) |
listing_duration_days |
int | 30 | Days until a listing expires (0 = never expires) |
allow_publishing |
bool | true | Allow users to create listings on the front-end |
guest_browse |
bool | true | Allow non-authenticated visitors to browse listings |
guest_contact |
bool | false | Allow guests to send messages to sellers |
moderation_enabled |
bool | true | Require admin approval before listings go live |
require_phone |
bool | false | Make phone number mandatory on listings |
enable_reports |
bool | true | Allow users to report listings |
enable_favorites |
bool | true | Allow users to save/bookmark listings |
allow_author_translations |
bool | true | Allow listing authors to provide translations |
Notification Settings
Each notification can be individually enabled or disabled from the admin settings panel:
| Setting Key | Default | Description |
|---|---|---|
notify_admin_on_new_listing |
true | Notify admins when a new listing is submitted |
notify_author_on_new_listing |
true | Send confirmation email to listing author |
notify_author_on_approval |
true | Notify author when their listing is approved |
notify_author_on_rejection |
true | Notify author when their listing is rejected |
notify_owner_on_new_message |
true | Notify listing owner when a new message is received |
notify_admin_on_report |
true | Notify admins when a listing is reported |
notify_owner_on_expiration |
true | Notify listing owner when their listing expires |
Admin: Settings
Access via Admin → Classified Ads → Settings. The settings page is organized into logical sections:
- Publishing: Toggle user listing creation, guest browsing, and guest contact.
- Moderation: Enable/disable admin approval for new listings.
- Listing Duration: Set how many days listings remain active (0 for unlimited).
- Images: Maximum number of images per listing.
- Contact Info: Whether phone number is required.
- Features: Enable/disable reporting and favorites.
- Translations: Allow authors to provide multi-language content.
- Notifications: Toggle each of the 7 notification types.
Displays the settings form with current values.
Saves all settings. Validated via UpdateClassifiedSettingsRequest.
Admin: Listings
The listings management page provides a paginated table of all listings with filtering by status and category. Each row shows the listing title, author, category, status badge, price, view count, message count, report count, and creation date.
Query Parameters
status | Filter by status (draft, pending_review, published, expired, rejected, sold) |
category_id | Filter by category ID |
Create & Edit
The listing form includes fields for:
- Content: Title (translatable), content/description (translatable), meta title, meta description
- Classification: Category selection, item condition (new, used, refurbished)
- Pricing: Price, currency, negotiable flag
- Location: City, region, country, address, latitude/longitude
- Contact: Contact name, email, phone
- Media: Featured image + gallery images (up to max configured)
- Custom Fields: Dynamic fields loaded based on the selected category (via AJAX)
- Status: Draft, pending review, published (admin can bypass moderation)
- Options: Featured toggle, expiration date, position
Listing creation form.
Store a new listing. Validated via Admin\StoreListingRequest. Handles image uploads and custom field values.
Edit form for an existing listing.
Update an existing listing. Supports replacing the featured image, adding/removing gallery images, and updating custom field values.
Delete a listing and all associated media, custom field values, messages, reports, and saved entries (via cascade).
Moderation
When moderation is enabled, new listings are set to pending_review status. Admins can approve or reject them:
Sets the listing status to published, records the publication date, and clears any rejection reason. Sends an approval notification to the author (if enabled).
Request Body
reason | Required: the rejection reason (sent to the author via email) |
Admin: Categories
Classified categories use the core unified categories table with categorizable_type = 'listing'. Categories support nesting (parent/child hierarchy via nested set) and are translatable.
Paginated nested tree view of all listing categories.
Create a new category. Validated via StoreCategoryRequest. Fields: name (translatable), slug (translatable), description (translatable), meta title, meta description, parent category, position, active status.
Update a category and rebuild the nested set tree.
Delete a category. Listings in the deleted category will have their category_id set to null.
Admin: Custom Fields
Custom fields allow you to extend listings with category-specific or global attributes. Fields can be marked as filterable (shown in search filters) and searchable (included in full-text search).
Paginated list of all custom fields with category filter.
Fields
name | Translatable: field label |
slug | Unique identifier |
type | One of the 10 supported field types |
category_id | Nullable: null means the field applies to all categories |
placeholder | Translatable: placeholder text |
help_text | Translatable: help text shown below the field |
options | JSON array: for select, checkbox, and radio types |
is_required | Whether the field is mandatory |
is_filterable | Show in search/filter sidebar |
is_searchable | Include in full-text search |
min_value / max_value | Validation bounds for numeric fields |
position | Display order |
Supported Field Types
| Type | Value | Has Options? | HTML Input |
|---|---|---|---|
| Text | text | No | <input type="text"> |
| Textarea | textarea | No | <textarea> |
| Number | number | No | <input type="number"> |
| Select | select | Yes | <select> |
| Checkbox | checkbox | Yes | <input type="checkbox"> |
| Radio | radio | Yes | <input type="radio"> |
| Date | date | No | <input type="date"> |
| Price Range | price_range | No | <input type="number"> |
| URL | url | No | <input type="url"> |
email | No | <input type="email"> |
GET /api/classified/custom-fields/{categoryId?} returns the relevant fields for that category (plus any global fields).
Admin: Messages
View all messages sent through listing contact forms. Messages can be filtered by read/unread status.
Paginated list of messages with sender, listing, date, and read status. Supports filtering by unread messages.
View message details. Automatically marks the message as read.
Delete a message permanently.
Admin: Reports
View and manage listing reports submitted by users. Reports include a reason (spam, inappropriate, fraud, wrong category, duplicate, or other) and an optional description.
Paginated list of reports with status filter (pending, reviewed, dismissed).
Review a report: set status to reviewed or dismissed. Records the reviewer and review timestamp.
Delete a report permanently.
Report Reasons
| Value | Description |
|---|---|
spam | Listing is spam or unsolicited advertising |
inappropriate | Content violates community guidelines |
fraud | Suspected fraudulent listing |
wrong_category | Listing is in the wrong category |
duplicate | Duplicate of another listing |
other | Other reason (user provides description) |
Front-End: Browsing & Search
The public classifieds page shows published, non-expired listings ordered by featured status, publication date, and position. Visitors can filter and search listings using multiple criteria.
Search & Filter Options
| Filter | Parameter | Description |
|---|---|---|
| Keyword Search | search | Full-text search in title, content, city, and region |
| Category | category_id | Filter by category |
| City | city | Location-based filtering (LIKE match) |
| Price Min | price_min | Minimum price threshold |
| Price Max | price_max | Maximum price threshold |
| Condition | condition | Item condition (new, used, refurbished) |
guest_browse to false in the admin settings. When disabled, the ClassifiedGuestAccess middleware redirects unauthenticated visitors to the login page.
Category Browsing
Access via /classifieds/category/{slug}. Shows listings filtered by the specified category, with full search and filtering available within that category.
Front-End: Listing Detail
Access via /classifieds/{slug}. The listing detail page includes:
- Full title, description, and custom field values
- Image gallery (featured image + additional gallery images)
- Price with currency formatting and negotiable indicator
- Item condition badge
- Location information (city, region, country, address)
- Seller contact information
- Contact form (for sending a message to the seller)
- Save/unsave button (for authenticated users)
- Report button (for authenticated users)
- Related listings (same category or same city)
- View count (incremented on each visit)
Front-End: Create & Edit Listings
Authenticated users can create and edit their own listings from the front-end. This feature can be disabled site-wide by setting allow_publishing to false.
Listing creation form. Requires authentication. Protected by CheckPublishingEnabled middleware.
Submit a new listing. Validated via Front\StoreListingRequest. When moderation is enabled, the listing is set to pending_review and requires admin approval.
Edit form for a listing. Only the listing owner can access this page.
Update a listing. Validated via Front\UpdateListingRequest.
pending_review status (even if the user selects “published”). Only admin-created listings or admin-approved listings bypass moderation.
Front-End: My Listings
Access via /classifieds/my/listings. Authenticated users can view all their listings with status-based tabs: all, active, pending, expired, sold, and draft.
From this page, users can:
- View listing status and message count
- Edit their listings
- Delete their listings
Delete a listing. Only the listing owner can perform this action.
Front-End: Saved Listings
Access via /classifieds/my/saved. Users can bookmark listings and view them later from a dedicated saved listings page.
Toggle save/unsave for a listing. Returns whether the listing is now saved (true) or unsaved (false). Works as an AJAX toggle.
enable_favorites to false in admin settings.
Front-End: Contact Seller
Each listing detail page includes a contact form that sends a message to the listing owner.
Request Body
sender_name | Required: sender’s name |
sender_email | Required: sender’s email |
sender_phone | Optional: sender’s phone |
message | Required: the message body |
Guest contact can be controlled via the guest_contact setting. When disabled, only authenticated users can send messages.
Front-End: Reporting
Authenticated users can report listings that violate community guidelines.
Request Body
reason | Required: one of: spam, inappropriate, fraud, wrong_category, duplicate, other |
description | Optional: additional details about the report |
Notifications
The add-on includes 7 email notification classes, all extending BaseNotification and using the mail channel. Each notification can be individually toggled via admin settings.
| Notification | Recipient | Trigger | Setting |
|---|---|---|---|
NewListingAdminNotification |
Admin users | New listing submitted | notify_admin_on_new_listing |
ListingConfirmationNotification |
Listing author | Listing submitted | notify_author_on_new_listing |
ListingApprovedNotification |
Listing author | Listing approved by admin | notify_author_on_approval |
ListingRejectedNotification |
Listing author | Listing rejected by admin | notify_author_on_rejection |
NewMessageNotification |
Listing owner | New contact message received | notify_owner_on_new_message |
ListingReportedNotification |
Admin users | Listing reported by a user | notify_admin_on_report |
ListingExpiredNotification |
Listing owner | Listing expires | notify_owner_on_expiration |
contact_email address on the listing using on-demand notification routing.
Updating
Step 1: Replace Files
Replace the add-on directory with the new version.
Step 2: Run Migrations
Step 3: Clear Caches
Step 4: Verify
Visit Admin → Classified Ads → Settings and confirm all settings are intact. Browse the classifieds page to verify listings display correctly.
Troubleshooting
Listings not visible on the front-end
Check that:
- The listing status is
published(not draft, pending, expired, rejected, or sold). - The
published_atdate is in the past. - The
expires_atdate is either null or in the future. - Guest browsing is enabled if you are not logged in (check
guest_browsesetting).
Users cannot create listings
- Ensure
allow_publishingis set totruein admin settings. - Verify the user is authenticated (listing creation requires login).
- Check the
CheckPublishingEnabledmiddleware is not blocking access.
Custom fields not appearing on the listing form
- Ensure the custom field is active (
is_active = true). - Verify the field is either global (
category_id = null) or assigned to the selected category. - Check the browser console for AJAX errors on the
/api/classified/custom-fields/endpoint. - Ensure JavaScript is enabled and the category select element triggers the AJAX call.
Listings stuck in “Pending Review”
- Moderation is enabled by default. Go to Admin → Classified Ads → Listings and approve pending listings.
- To disable moderation, set
moderation_enabledtofalsein admin settings. New listings will be published immediately.
Notifications not being sent
- Verify the corresponding notification setting is enabled (e.g.,
notify_admin_on_new_listing). - Ensure your mail configuration is correct in
.env(SMTP, Mailgun, etc.). - Check the Laravel log (
storage/logs/laravel.log) for mail errors. - If the listing has no associated user, ensure the
contact_emailis set on the listing for fallback delivery.
Expired listings still showing
- The
expireOldListings()method inClassifiedServicemust be called to update status. This should be scheduled as a cron job or artisan command. - Listings with
expires_atin the past are automatically excluded from theactive()scope, but their status field is only updated when the expiration process runs.
Images not uploading
- Check PHP’s
upload_max_filesizeandpost_max_sizesettings. - Verify the
storagedirectory is writable. - Ensure you have not exceeded the
max_imageslimit configured in admin settings.
Category listing counts incorrect
- Listing counts are computed dynamically via
withCount. If counts seem stale, clear the application cache:php artisan cache:clear.
Save/favorite toggle not working
- Ensure the user is authenticated (saving requires login).
- Verify
enable_favoritesis set totruein admin settings. - Check the browser console for AJAX errors on the toggle endpoint.
- Ensure the CSRF token is included in the request headers.