> For the complete documentation index, see [llms.txt](https://tyrix.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tyrix.gitbook.io/docs/script-resources/tyrix-droply/configuration.md).

# Configuration

Droply splits its configuration across two files:

* `config.lua` — shared client + server settings (loaded as a `shared_script`). Branding, economy, and limits.
* `config_sv.lua` — server-only. Discord webhook URLs live here and are never sent to a client.

Both are auto-loaded by `fxmanifest.lua` and both are escrow-ignored, so you can edit them freely.

{% hint style="warning" %}
Never reference `Config_SV` from client-side code. Webhook URLs must stay server-side.
{% endhint %}

## Debug

```lua
Config.Debug = false
```

Enables verbose console output — cache population counts, worker focus changes, order assignment traces. Leave off in production.

## App identifier

```lua
Config.AppIdentifier = 'droply'
```

The `AddCustomApp` identifier and the `SendCustomAppMessage` channel. Only change this if it collides with another lb-phone app; changing it does not require any UI rebuild.

## Branding

Re-skin the whole app from one table.

```lua
Config.Brand = {
    name      = 'Droply',
    accent    = '#EF4444',  -- primary accent, dark-mode-first red
    accentDim = '#DC2626',  -- pressed / secondary accent
    logoText  = 'Droply',
}
```

`name` drives the app name shown on the phone home screen. `accent` / `accentDim` are pushed into the UI's CSS custom properties. To change the app icon, replace `ui/dist/icon.svg`.

## Order lifecycle

```lua
Config.Droply = {
    acceptTimeoutSec          = 600,
    trackingIntervalMs        = 2500,
    handoverDistance          = 4.0,
    avgSpeedMps               = 11.0,
    maxConcurrentPerWorker    = 3,
    oneActiveOrderPerCustomer = true,
    offlineGraceSec           = 120,
}
```

| Setting                     | Default | Effect                                                                              |
| --------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `acceptTimeoutSec`          | `600`   | Seconds a `pending` order waits for a driver before it auto-expires.                |
| `trackingIntervalMs`        | `2500`  | How often the driver's client posts its position while `enroute`.                   |
| `handoverDistance`          | `4.0`   | Base handover proximity in metres. The server check adds a 2 m tolerance on top.    |
| `avgSpeedMps`               | `11.0`  | Assumed driver speed used to compute the customer's ETA.                            |
| `maxConcurrentPerWorker`    | `3`     | How many live orders one driver may hold at once.                                   |
| `oneActiveOrderPerCustomer` | `true`  | Restricts each customer to a single live order.                                     |
| `offlineGraceSec`           | `120`   | Grace period before an `enroute` order is cancelled after the customer disconnects. |

**On `maxConcurrentPerWorker`.** The tyrix\_business DeliveryPanel HUD can only render one delivery at a time, so `client/worker.lua` focuses a single order and auto-switches as orders go `enroute` or complete. An `enroute` order always outranks a `preparing` one; ties break to the lowest order id, so focus is stable and predictable.

## Tracking & handover tuning

```lua
Config.Droply = {
    workerMoveGateMeters     = 15,
    relayCoalesceMeters      = 2,
    approachNotifyMeters     = 150,
    handoverInteractDistance = 2.5,
    handoverProgressMs       = 4000,
}
```

| Setting                    | Default | Effect                                                                               |
| -------------------------- | ------- | ------------------------------------------------------------------------------------ |
| `workerMoveGateMeters`     | `15`    | Client-side gate — only report position after moving this far since the last report. |
| `relayCoalesceMeters`      | `2`     | Server-side gate — drop sub-N-metre motion before relaying to the customer.          |
| `approachNotifyMeters`     | `150`   | Radius for the one-shot "your driver is approaching" alert.                          |
| `handoverInteractDistance` | `2.5`   | Radius at which the third-eye handover option becomes visible.                       |
| `handoverProgressMs`       | `4000`  | Duration of the handover progress bar.                                               |

These four gates exist to keep the tracking pipeline cheap at scale. Lowering `workerMoveGateMeters` and `relayCoalesceMeters` makes the customer's blip smoother at the cost of more network traffic per active delivery.

## Economy

```lua
Config.Droply = {
    deliveryFeeRecipient = 'society',   -- 'society' | 'worker'
    defaultCommission    = 0.30,
    paymentSources       = { 'bank', 'cash' },
}
```

| Setting                | Default           | Effect                                                                |
| ---------------------- | ----------------- | --------------------------------------------------------------------- |
| `deliveryFeeRecipient` | `'society'`       | Who receives the storefront's delivery fee.                           |
| `defaultCommission`    | `0.30`            | Business's cut of the subtotal when a storefront has not set its own. |
| `paymentSources`       | `{'bank','cash'}` | Accounts customers pay from, drained **in this order**.               |

**Commission.** A storefront's own `commission` column overrides `defaultCommission`. On delivery the split is:

```
societyCut = floor(subtotal × commission)
workerCut  = subtotal − societyCut

business receives  = societyCut + (delivery fee, if deliveryFeeRecipient == 'society')
driver receives    = workerCut  + tip + (delivery fee, if deliveryFeeRecipient == 'worker')
```

The **tip always goes to the driver**, in full. Driver payouts land in cash; business payouts go to the society account. See [Order Lifecycle → Money](/docs/script-resources/tyrix-droply/order-lifecycle.md#money).

**Payment sources.** `Bridge.ChargePlayer` sums every listed source to check affordability, then drains them in order. With the default, a customer's bank is emptied before their cash is touched. Set `{ 'cash' }` to force cash-only delivery.

## Cart & input limits

```lua
Config.Droply = {
    maxQtyPerItem     = 25,
    maxDistinctItems  = 12,
    maxTip            = 10000000,
    maxItemPrice      = 1000000,
    maxManagers       = 15,
    minDisplayName    = 2,
    maxDisplayName    = 48,
    reviewBodyMaxLen  = 500,
}
```

Every one of these is enforced **server-side** on the relevant event. The UI mirrors them for a good experience, but a modded client cannot exceed them.

## Cancellation fees

Charged when a **customer** cancels an order a driver has already started preparing. Cancelling while still `pending` (no driver assigned) is always free.

```lua
Config.Droply.cancellation = {
    enabled              = true,
    percent              = 0.25,        -- fraction of the order subtotal
    minFee               = 50,          -- floor in $, even on tiny orders
    recipient            = 'business',  -- 'business' | 'worker' | 'split'
    workerShare          = 0.5,         -- worker's cut (0-1) when recipient == 'split'
    requireFundsToCancel = false,
}
```

The fee is `max(minFee, floor(subtotal × percent))`.

| `recipient`  | Where the fee goes                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------------------ |
| `'business'` | Entirely to the society account.                                                                                   |
| `'worker'`   | Entirely to the driver, in cash. Falls back to the business if the driver is offline.                              |
| `'split'`    | `workerShare` to the driver, remainder to the business. The business takes the whole fee if the driver is offline. |

**`requireFundsToCancel`** decides what happens when a broke customer tries to cancel:

* `false` *(default)* — the cancel succeeds, the fee is waived, and the waiver is logged to Discord.
* `true` — the cancel is **rejected** and the order stays `preparing`.

Set `enabled = false` to make all cancellations free.

## Blocklist

```lua
Config.Droply.maxBlockedCustomers = 50
```

Cap on how many customers one business may block. See [Anti Abuse](/docs/script-resources/tyrix-droply/anti-abuse.md).

## Promotions

```lua
Config.Droply = {
    maxPromosPerBusiness = 25,
    maxDiscountPercent   = 50,
    promoCodeMaxLen      = 32,
}
```

Codes are normalised to uppercase and must match `^[A-Z0-9_-]+$`, minimum 2 characters. Full behaviour on the [Promotions](/docs/script-resources/tyrix-droply/promotions.md) page.

## Bridge

```lua
Config.Bridge = {
    ResourceNames = {
        ESX    = 'es_extended',
        QBCore = 'qb-core',
        QBox   = 'qbx_core',
    },
    Notifications = {
        system = 'ox_lib',
        custom = nil,
    },
}
```

**`ResourceNames`** — only change these if your framework resource is renamed on your server. Detection runs in order (ESX → QBCore → QBox) and stops at the first started resource.

**`Notifications`** — selects the notification backend. Set `system = 'custom'` and supply a `custom` handler in `bridge/client.lua` to route Droply notifications through your own system.

## Discord logging — `config_sv.lua`

Server-only and escrow-ignored.

```lua
Config_SV.DiscordLogs = {
    enabled   = false,
    botName   = 'Droply',
    botAvatar = '',

    webhook = '',        -- single shared webhook

    webhooks  = { order = '', accept = '', delivered = '', --[[ … ]] },
    logTypes  = { order = true, accept = true, delivered = true, --[[ … ]] },
    colors    = { order = 3447003, delivered = 5763719, --[[ … ]] },
    thumbnails = { default = '', order = '', --[[ … ]] },
}
```

Set `enabled = true` and either a single `webhook` or per-type entries in `webhooks` (per-type wins where set). Full reference on the [Discord Logging](/docs/script-resources/tyrix-droply/discord-logging.md) page.
