Marketplace
A crosshair marketplace inside the menu. Players publish their own crosshairs, browse everyone else's, apply one in a click, like the good ones, and open an author's profile to see the rest of their work. Moderators get a tab of their own.
The whole thing is one switch:
F5Cfg.EnableMarketplace = true
With it off, the tab disappears and every marketplace call refuses — including browsing, which answers "Marketplace is disabled" rather than an empty list. Existing listings stay in the database untouched.
What a Listing Holds
A listing carries the crosshair only: the crosshair itself, its custom image, and the player's per-weapon crosshairs. Nothing else from their HUD is published — no effects, no killstreak, no K/D.
| Stored | From |
|---|---|
| Title, description, tags | The publisher, sanitised and length-capped |
| The crosshair | Re-validated server-side before it is written |
| Owner name | The framework character name at the moment of publishing, capped at 60 characters. It is never refreshed afterwards |
| Display name and avatar | The FiveM name and the Steam avatar, refreshed on every edit |
| Likes, applies | Counters, one per player per listing |
Avatars need set steam_webApiKey "…" in server.cfg. Without it they stay blank everywhere — the console says so once at startup.
Publishing
| Rule | Default | Config |
|---|---|---|
| Listings per player | 10 | Marketplace.maxCrosshairListings |
| Title | required, 50 characters | Marketplace.titleMaxLength |
| Description | optional, 160 characters | Marketplace.descriptionMaxLength |
| Tags | 2–3 of the 9 available | Marketplace.tags.minTagsPerListing / maxTagsPerListing |
| Cooldown | one publish or edit every 3 seconds | — |
The listing cap is enforced inside the insert itself, so two fast clicks cannot get around it. Title and description are stripped of control characters and collapsed whitespace, then truncated on a UTF-8 boundary. Tags that are not in tags.available are dropped silently.
The 3-second limit is applied before the listing is checked. A publish rejected for a missing title, too few tags or a blocked image has already used the player's turn — they have to wait before retrying with it fixed.
A player may edit or unpublish their own listings only — both queries are scoped to the owner's licence, so a crafted id changes nothing.
Apply replaces the player's live crosshair, custom image and per-weapon crosshairs, resets their active profile to Default, and saves — with no confirmation step. Players who want their old crosshair back should save it as a profile first.
Custom-image listings
F5Cfg.MarketplaceSections = {
publish = true,
publishCustomImage = true,
}
With publishCustomImage = false a listing whose crosshair carries an image is refused at publish and edit time — "Custom image crosshairs cannot be published on this server". The gate looks at whether the profile carries an image for its current source — an uploaded picture in upload mode, an address in URL mode — not at whether the image is switched on. A player with a leftover upload has to clear it before they can publish a plain shape crosshair.
It is a publish-time gate only: listings that went up while it was on keep working, and can still be browsed and applied afterwards.
URL-mode images are snapshotted, not linked. When a player publishes a crosshair whose image is an external URL, the server downloads it once and stores the bytes inside the listing. That means the listing keeps working after the original link dies — and it means the download has rules:
| Check | Behaviour |
|---|---|
| Fetch failed, non-200, empty body | "The image could not be downloaded from that URL" — the listing is not published |
Larger than snapshotMaxBytes (384 KB) | "That image is too large to publish" |
Encoded result over imageDataMaxBytes (512 KB) | Same message |
| Content type not PNG / JPEG / GIF / WEBP / SVG, or the bytes do not match the type | "That link does not point to a supported image file" |
Upload-mode images are already stored bytes and skip this path entirely.
Tags
Nine tags ship: competitive, fun, roleplay, minimal, colorful, animated, sniper, close-range, custom-image.
custom-image is reserved. Players cannot pick it; the server adds it when the published crosshair actually renders an image and strips it otherwise. It does not count toward the maximum a player may pick, and it does not count toward the minimum they must pick — but it does have to stay in tags.available to be filterable, and startup warns you if it is missing.
Browsing
| Control | Behaviour | Turn off with |
|---|---|---|
| Search | Matches the title, or the author's name in author mode. Up to 50 characters; SQL wildcards are escaped | MarketplaceSections.search |
| Sort | Newest, most applied, most liked | MarketplaceSections.sort |
| Tag filter | Up to 10 tags at once, matched as OR | MarketplaceSections.tags |
| Author profile | Up to 100 of that author's listings | MarketplaceSections.authorProfile |
| Apply | One click | MarketplaceSections.apply |
| Likes | One per player per listing, no unlike, never your own | MarketplaceSections.likes |
Turning search off does not just hide the box — the server discards any search text that arrives. Turning sort off pins every browse to defaultSort.
Hidden listings and listings by banned players are filtered out for everyone except a moderator who explicitly asks to see them.
Results past 5000 rows are unreachable — with the default page size of 12 that is 417 pages. And one page of results carries at most 2 MiB of inlined images: listings past that budget arrive without their picture and are marked as such, rather than being dropped.
Moderation
The Moderation tab appears for players who match F5Cfg.ModeratorAces — see Permissions. Every action is re-checked server-side, so the tab itself grants nothing.
| Action | Effect |
|---|---|
| Hide | The listing stays in the database but leaves circulation. A reason can be attached, up to 255 characters. It still counts against the owner's listing limit — they see it in their own profile marked as hidden, and can delete it themselves |
| Unhide | Puts it back and clears the reason |
| Remove | Deletes the listing outright, along with its likes and applies |
| Ban | Blocks a player from the marketplace and deletes every listing they own |
| Unban | Lifts the ban. Deleted listings are not restored |
The panel loads one page of up to 100 listings — newest first, hidden ones included — and has no paging of its own. Moderation actions are limited to one per second per moderator.
Bans
Durations are a closed set: 1, 3, 7, 30, 90 days, or permanent. Anything else is refused before anything is written.
A banned player is blocked from publishing, editing, applying and liking. They can still browse. If they are online when the ban lands they are told immediately:
You are banned from the marketplace until 2026-09-01 18:30. Reason: stolen artwork
An empty reason reads "no reason given". Banning the same licence again updates the existing ban rather than stacking. An expired ban stops applying on its own — no restart needed.
authorProfile off, a hidden listing is unreachableAn owner normally finds their hidden listing in their own profile view and can remove it. Switch MarketplaceSections.authorProfile off and that view is gone — the listing is invisible to everyone including its owner, while still occupying one of their slots. On a server with author profiles disabled, use Remove rather than Hide.
The publish, edit, apply and like calls all check the ban list. Unpublish does not — a banned player can still remove their own remaining listings. Since banning already deletes their listings, this rarely comes up.
The audit trail
Every moderation action writes a row into f5_combathud_admin_audit — actor, action, target, the old and new values as JSON — and fires a Discord webhook if one is configured. The database row is the authoritative record: it stores the licence, while the Discord embed shows the player name the moderator's client supplied.
Rate Limits
| Action | Limit |
|---|---|
| Publish, edit | 1 per 3 s |
| Apply | 1 per 3 s |
| Like | 1 per 2 s |
| Browse | Burst of 10, refilling 3 per second |
| Author profile | Burst of 5, refilling 1 per second |
| Every moderation action | 1 per s |
Browsing past the limit answers with a short retry delay rather than an error message, so the menu simply waits.
Player-Facing Messages
All of these come from locales/, so they are translated:
| Situation | Message |
|---|---|
| Marketplace off | Marketplace is disabled |
| Database not ready yet | Database is not ready — contact the server admin |
| No title | Listing title is required |
| Too few tags | Pick at least 2 tags before publishing |
| At the listing cap | You can publish max 10 crosshairs |
| Published | Crosshair published on marketplace |
| Editing someone else's listing | Listing not found or no permission |
| Listing hidden or deleted meanwhile | Listing not found |
| Liking your own listing | You cannot like your own listing |
| Liking twice | You already liked this listing |
| Applying too fast | Please wait before applying again |
| Custom image blocked | Custom image crosshairs cannot be published on this server |
| Image URL fetch failed | The image could not be downloaded from that URL |
| Image too large | That image is too large to publish |
| Wrong image type | That link does not point to a supported image file |
| Not a moderator | You are not allowed to moderate the marketplace |
| Banned | You are permanently banned from the marketplace. Reason: … |
See Also
- Configuration → Marketplace — every limit and section flag
- Permissions — who gets the Moderation tab
- Discord Webhooks — what each marketplace event logs
- Database — the four marketplace tables
- Features → Custom image crosshair — the image rules players hit before they ever publish