# Settings Sharing (/docs/orbit-studios-resources/orbit-dynamichud/settings-sharing)



# Settings Sharing [#settings-sharing]

Settings Sharing lets players save multiple HUD presets, import validated JSON, exchange temporary private codes, and publish presets to a searchable community gallery. The server remains authoritative over ownership, limits, likes, downloads, moderation, and the complete settings schema.

## Requirements [#requirements]

* `ox_lib` callbacks
* `oxmysql` and `@oxmysql/lib/MySQL.lua`
* One capture provider, either `screenshot-basic` or `screencapture`, only when uploaded thumbnails are enabled
* A server-side FiveManage API key only when uploaded thumbnails are enabled

Use the [FiveManage Setup guide](/docs/orbit-studios-resources/orbit-dynamichud/fivemanage) to create a least-privilege Images token, store it as a server convar, configure the capture provider, and verify the complete upload path.

```properties title="server.cfg"
ensure ox_lib
ensure oxmysql

# Configure credentials before orbit-dynamichud-v2 starts.
set orbit_fivemanage_api_key "YOUR_FIVEMANAGE_API_KEY"

# Start only the provider selected by captureProvider.
ensure screencapture
# ensure screenshot-basic

ensure orbit-lib
ensure orbit-dynamichud-v2
```

The resource manifest must include:

```lua title="fxmanifest.lua"
dependency 'oxmysql'
server_script '@oxmysql/lib/MySQL.lua'
```

## Feature configuration [#feature-configuration]

All player-facing feature gates live in `Config.ControlPanel.settingsShare`.

```lua title="resources/[orbit]/orbit-dynamichud-v2/shared/config.lua"
Config.ControlPanel.settingsShare = {
    enabled = true,
    controlPanelTab = true,
    standalonePage = true,
    discovery = true,
    localPresets = true,
    manualImport = true,
    privateSharing = true,
    publicSharing = true,
    moderation = true,
    requireApproval = true,
    thumbnails = true,
    maxPresets = 12,
    maxPublicPresets = 4,
    maxPresetNameLength = 30,
    maxPayloadBytes = 131072
}
```

| Setting               | Default  | Effect                                                                                                                  |
| --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `enabled`             | `true`   | Master switch. When false, client and server Settings Share logic stop.                                                 |
| `controlPanelTab`     | `true`   | Show the UI Presets launcher in the control panel when `standalonePage` is also enabled.                                |
| `standalonePage`      | `true`   | Allow the full Settings Share page, including entry from the control panel and `/settingsshare`.                        |
| `discovery`           | `true`   | Browse, search, filter, like, and download published presets.                                                           |
| `localPresets`        | `true`   | Save and update database-backed private presets.                                                                        |
| `manualImport`        | `true`   | Import a complete settings JSON document.                                                                               |
| `privateSharing`      | `true`   | Generate/import temporary private share codes.                                                                          |
| `publicSharing`       | `true`   | Submit or publish presets to discovery.                                                                                 |
| `moderation`          | `true`   | Enable administrator Awaiting and Declined views.                                                                       |
| `requireApproval`     | `true`   | Send public uploads to Awaiting review. It only applies when moderation is also enabled.                                |
| `thumbnails`          | `true`   | Enable the selected capture provider and FiveManage screenshot pipeline. Fallback previews remain available when false. |
| `maxPresets`          | `12`     | Total private, awaiting, declined, and public database presets owned by one player. Clamped to `1-50`.                  |
| `maxPublicPresets`    | `4`      | Public preset cap per player. Clamped to `1-20` and never above `maxPresets`.                                           |
| `maxPresetNameLength` | `30`     | Name limit. The server absolute maximum is 30 bytes.                                                                    |
| `maxPayloadBytes`     | `131072` | Maximum serialized settings payload. Clamped to `16384-524288`.                                                         |

`Config.SettingsLocked = true` disables Settings Share because imported or downloaded data cannot be allowed to replace server-enforced defaults.

### Common feature combinations [#common-feature-combinations]

```lua title="Private presets only"
Config.ControlPanel.settingsShare = {
    enabled = true,
    controlPanelTab = true,
    standalonePage = true,
    discovery = false,
    localPresets = true,
    manualImport = true,
    privateSharing = false,
    publicSharing = false,
    moderation = false,
    requireApproval = false,
    thumbnails = false,
    maxPresets = 12,
    maxPublicPresets = 1,
    maxPresetNameLength = 30,
    maxPayloadBytes = 131072
}
```

```lua title="Public uploads without approval"
Config.ControlPanel.settingsShare.publicSharing = true
Config.ControlPanel.settingsShare.discovery = true
Config.ControlPanel.settingsShare.moderation = false
Config.ControlPanel.settingsShare.requireApproval = false
```

```lua title="Hide only the control-panel launcher"
Config.ControlPanel.settingsShare.controlPanelTab = false
Config.ControlPanel.settingsShare.standalonePage = true
```

## Server limits and moderation [#server-limits-and-moderation]

```lua title="resources/[orbit]/orbit-dynamichud-v2/server/config.lua"
Config.SettingsShareServer = {
    adminAce = 'orbit.settingsshare.admin',
    maxStoredPresets = 250,
    maxListResults = 60,
    requestWindowSeconds = 10,
    privateShareTtlSeconds = 900,
    presetUploadCooldownSeconds = 300,
    thumbnail = {
        captureProvider = 'screencapture',
        apiKey = GetConvar('orbit_fivemanage_api_key', ''),
        presignedUrlEndpoint = 'https://api.fivemanage.com/api/v3/file/presigned-url',
        fileEndpoint = 'https://api.fivemanage.com/api/v3/file',
        deleteUnusedThumbnails = true,
        allowedHosts = {
            'r2.fivemanage.com',
            'cdn.fivemanage.com'
        },
        encoding = 'jpg',
        quality = 0.85,
        timeoutMs = 15000,
        maxWidth = 1920,
        maxHeight = 1080,
        captureAttempts = 3,
        captureRetryDelayMs = 350,
        uploadAttempts = 3,
        retryDelayMs = 600
    }
}
```

| Setting                            | Default                     | Server bounds/purpose                                                                                           |
| ---------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `adminAce`                         | `orbit.settingsshare.admin` | ACE required for moderation actions.                                                                            |
| `maxStoredPresets`                 | `250`                       | Global database preset cap, clamped to `10-10000`.                                                              |
| `maxListResults`                   | `60`                        | Maximum rows returned per list request, clamped to `10-200`.                                                    |
| `requestWindowSeconds`             | `10`                        | Per-player action-rate window, clamped to `2-60` seconds.                                                       |
| `privateShareTtlSeconds`           | `900`                       | In-memory private-code lifetime, clamped to `60-86400` seconds.                                                 |
| `presetUploadCooldownSeconds`      | `300`                       | Delay after a successful save/update, clamped to `0-3600`. `0` disables it.                                     |
| `thumbnail.captureProvider`        | `screencapture`             | Capture resource to use: `screenshot-basic` or `screencapture`. Invalid values fall back to `screenshot-basic`. |
| `thumbnail.apiKey`                 | convar                      | FiveManage key. Keep it server-side.                                                                            |
| `thumbnail.presignedUrlEndpoint`   | FiveManage v3 endpoint      | Endpoint used to request a presigned upload URL.                                                                |
| `thumbnail.fileEndpoint`           | FiveManage v3 file endpoint | Endpoint used to delete managed thumbnail files.                                                                |
| `thumbnail.deleteUnusedThumbnails` | `true`                      | Delete a managed CDN file after its preset is deleted or its thumbnail is replaced.                             |
| `thumbnail.allowedHosts`           | FiveManage CDN hosts        | HTTPS hosts accepted for returned thumbnail URLs.                                                               |
| `thumbnail.encoding`               | `jpg`                       | `jpg` or `png`.                                                                                                 |
| `thumbnail.quality`                | `0.85`                      | Screenshot quality clamped to `0.1-1.0`.                                                                        |
| `thumbnail.timeoutMs`              | `15000`                     | Upload timeout clamped to `5000-30000` milliseconds.                                                            |
| `thumbnail.maxWidth`               | `1920`                      | Maximum captured width, clamped to `640-4096`.                                                                  |
| `thumbnail.maxHeight`              | `1080`                      | Maximum captured height, clamped to `360-4096`.                                                                 |
| `thumbnail.captureAttempts`        | `3`                         | Capture-stage attempts, clamped to `1-5`. Each attempt waits for a freshly presented frame.                     |
| `thumbnail.captureRetryDelayMs`    | `350`                       | Base delay between capture attempts, clamped to `100-3000` milliseconds.                                        |
| `thumbnail.uploadAttempts`         | `3`                         | Fresh capture/upload attempts, clamped to `1-5`.                                                                |
| `thumbnail.retryDelayMs`           | `600`                       | Base retry delay, clamped to `100-3000` milliseconds and increased per attempt.                                 |

Grant the configured moderation ACE in `server.cfg`:

```properties title="server.cfg"
add_ace group.admin orbit.settingsshare.admin allow
```

When `requireApproval` is true, a public submission enters `awaiting`. An administrator can publish or decline it. Declined entries are exposed in the admin UI from runtime cache and can be restored while the resource remains running. When approval is disabled, valid public uploads publish immediately.

## Upload cooldown [#upload-cooldown]

The default cooldown is five minutes. It starts after every successful private or public save/update. While active, the server rejects:

* another private preset save
* another public preset submission/update
* thumbnail preparation
* screenshot capture/upload ticket issuance

The cooldown is enforced by owner identifier on the server. UI checks are only presentation; they are not the security boundary.

## Database storage [#database-storage]

The resource creates these InnoDB tables automatically:

| Table                         | Purpose                                                                                                                  |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `orbit_settings_presets`      | Preset owner, name, creator, settings, preview metadata, visibility/review state, thumbnail, statistics, and timestamps. |
| `orbit_settings_preset_likes` | One like per preset and owner identifier, deleted automatically with its preset.                                         |

The preset table stores an internal auto-increment database ID plus an opaque public ID. Indexed owner, visibility, review, popularity, rating, creator, and name fields keep lists/searches bounded. Listing endpoints return validated preview data; complete settings JSON is loaded only when needed.

Do not manually edit settings JSON in the database. Use the UI so schema validation, ownership checks, metadata updates, and statistics stay consistent.

## Private share codes [#private-share-codes]

Private share codes look like:

```text title="Private share code example"
DJ7F-K9L2-8MNP-Q4XR
```

They contain 16 unambiguous uppercase letters/numbers in four groups. Private codes:

* are held in server memory, not the database
* contain the validated preset settings and minimal metadata needed for import
* are limited to one active code per player
* replace that player's previous code
* expire after `privateShareTtlSeconds` (15 minutes by default)
* expire when removed, when the owner disconnects, or when the resource/server restarts

Importing a private code creates an independent preset for the recipient. Later changes to either copy do not synchronize.

## Manual JSON import [#manual-json-import]

Manual import accepts only a complete settings document matching the current server schema. The server checks:

* valid JSON and payload size
* every required root and nested key
* no unknown/extra keys
* allowed enum values and finite numeric ranges
* complete layout objects
* valid status colors, icons, opacity, widgets, and priorities
* supported HUD, shape, player-info, speedometer, and performance values

This strict check applies to manual imports, private codes, saves, updates, public uploads, and downloads before settings can be applied. Do not remove keys from exported JSON or add custom metadata inside the settings object.

Preset names permit ASCII letters, numbers, single spaces, apostrophes, and hyphens. They must start and end with a letter/number, cannot contain repeated separator characters, and cannot exceed the configured limit or the server maximum of 30 bytes. URLs, emoji, control characters, and arbitrary markup are rejected.

## Public discovery [#public-discovery]

Discovery supports:

* search by preset name or creator name
* Most Popular, Top Rated, and Latest sorting
* gallery and list views
* liked-only filtering
* unique per-player likes
* server-counted downloads
* owner/admin delete controls
* public updates by the owner

Likes and downloads are updated on the server. The popularity score combines downloads and likes; clients cannot submit authoritative counts.

## Thumbnail capture [#thumbnail-capture]

When thumbnails are disabled or unavailable, the UI uses the built-in status/speedometer fallback preview. When enabled:

1. The server verifies ownership, feature access, limits, cooldown, and request rate.
2. The server requests a short-lived FiveManage presigned URL.
3. The active NUI serializes the current visible HUD state for capture.
4. A capture-only DUI loads the same compiled frontend and renders that state at the active game resolution.
5. The client draws the rendered HUD as a full-screen runtime texture over the game frame and temporarily enables the minimap.
6. The configured `screenshot-basic` or `screencapture` provider captures the composed game and HUD frame and uploads it directly to the presigned URL.
7. Transient capture/upload failures are retried within the configured bounds; every attempt requests a fresh frame.
8. The client restores the normal HUD and the player's previous radar state even when capture fails.
9. The upload is stored in the shared `orbit-dynamichud` directory with its short-lived marker in the filename. The server accepts only an HTTPS result from an allowed hostname whose storage key contains that marker, then verifies the uploaded JPEG/PNG bytes and retains the verified key for safe cleanup.

This is the actual DynamicHUD frontend rendered through its normal components in capture mode, rather than a separately styled approximation. The game world, minimap, and visible HUD components are combined before the selected provider captures the frame.

Only the configured capture resource must be installed and started. Do not start both solely for DynamicHUD:

```lua title="resources/[orbit]/orbit-dynamichud-v2/server/config.lua"
Config.SettingsShareServer.thumbnail.captureProvider = 'screencapture'
-- or: 'screenshot-basic'
```

```properties title="server.cfg"
set orbit_fivemanage_api_key "YOUR_FIVEMANAGE_API_KEY"

# Match the resource to captureProvider.
ensure screencapture
# ensure screenshot-basic

ensure orbit-dynamichud-v2
```

If custom capture fails, the preset can still be saved or published without a thumbnail and will use the fallback preview. A failed capture does not produce a trusted thumbnail token.

Never expose the FiveManage API key in shared/client config, NUI messages, or frontend environment variables.

See [FiveManage Setup](/docs/orbit-studios-resources/orbit-dynamichud/fivemanage) for account/team creation, token scope, start order, verification, rotation, and troubleshooting.

When `thumbnail.deleteUnusedThumbnails` is enabled, DynamicHUD also asks FiveManage to remove a managed image after its preset is deleted or its thumbnail is replaced. Failed cleanup is non-blocking and never makes the preset operation fail.

## Commands and access [#commands-and-access]

`/settingsshare` opens the standalone page when these conditions are met:

* `Config.Commands.settingsshare.enabled = true`
* Settings Share `enabled = true`
* `standalonePage = true`
* at least one Settings Share feature is available
* `Config.SettingsLocked = false`

The control-panel launcher separately requires `controlPanelTab = true`, `standalonePage = true`, and an enabled control panel.

## Operational checklist [#operational-checklist]

* Confirm `oxmysql` starts before DynamicHUD and no database initialization error appears.
* Keep `maxStoredPresets`, per-player limits, list limits, and cooldowns appropriate for the server size.
* Grant the moderation ACE only to trusted groups.
* Test private, awaiting, published, declined, like, download, update, and delete flows.
* Restart the resource once to verify private codes expire as intended.
* If thumbnails are enabled, verify the selected provider is started, capture the HUD and minimap, and confirm the returned hostname is in `allowedHosts`.
* Back up the two Settings Share tables with the rest of the server database.
