# esx_joblisting (/docs/addons/esx_joblisting)

esx_joblisting is the job center: a map marker that opens a NUI where players browse non-whitelisted jobs, read a description, set a waypoint, apply, or quit to unemployed. It also exposes a small task and progress API that other job scripts can use to show goals in the UI.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_joblisting` (version `1.0`) |
| Side | client + server, NUI (`web/index.html`) |
| Dependencies | `esx_lib`, `es_extended` |
| Also used | `oxmysql` |
| Database | `jobs.whitelisted` column |

Which jobs appear is controlled by the database, not by the config: every job with `whitelisted = false` that has a grade `0` is listed. `Config.JobDetails` only adds text, images and tasks to those cards.

## Installation [#installation]

<Steps>
<Step>
### Make sure the `whitelisted` column exists [#make-sure-the-whitelisted-column-exists]

`esx_joblisting.sql` runs:

```sql
ALTER TABLE jobs add whitelisted BOOLEAN NOT NULL DEFAULT FALSE;
```

Current ESX Legacy databases (including the txAdmin recipe) already have this column. Running the file again fails with "Duplicate column name", which you can ignore. Only run it on very old databases.
</Step>
<Step>
### Start it [#start-it]

```bash title="server.cfg"
ensure es_extended
ensure esx_lib
ensure esx_joblisting
```
</Step>
<Step>
### Hide jobs that need an application [#hide-jobs-that-need-an-application]

```sql
UPDATE jobs SET whitelisted = 1 WHERE name IN ('police', 'ambulance', 'mechanic');
```

Restart the server so ESX reloads the jobs.
</Step>
</Steps>

## Configuration [#configuration]

| Key | Default | Description |
| --- | --- | --- |
| `Config.DrawDistance` | `15.0` | Marker draw distance. Also the server-side radius for opening the menu and changing jobs. |
| `Config.ZoneSize` | `{ x = 2.7, y = 2.7, z = 0.5 }` | Marker size. The interaction radius is half of `x`. |
| `Config.MarkerColor` | `{ r = 100, g = 200, b = 104 }` | Marker color. |
| `Config.MarkerType` | `27` | Marker type. |
| `Config.Debug` | `ESX.GetConfig().EnableDebug` | Read from es_extended. Not used by the current code. |
| `Config.Locale` | `GetConvar('esx:locale', 'en')` | Language. |
| `Config.JobDetails` | `miner`, `delivery`, `garbage` | Extra card data per job name, see below. |
| `Config.UnemployedJob` | `'unemployed'` | Job set when a player clicks **Quit**. |
| `Config.Zones` | `{ vector3(-265.08, -964.1, 30.3) }` | Job center locations. |
| `Config.Blip` | `{ Enabled = true, Sprite = 407, Display = 4, Scale = 0.8, Colour = 27, ShortRange = true }` | Map blip for each zone. |

### `Config.JobDetails` [#configjobdetails]

Keyed by job name. Every field is optional.

| Field | Type | Description |
| --- | --- | --- |
| `subtitle` | `string` | Short line under the job name. |
| `description` | `string` | Long text. `\n\n` creates paragraphs. |
| `image` | `string` | Path inside `web/`, for example `'assets/miner.png'`. |
| `rating` | `number` | Difficulty, 0 to 5 stars. |
| `requirement` | `string` | Shown as a badge. Defaults to "No requirements". |
| `location` | `{ x, y }` | Enables the **mark location** button, which sets a GPS waypoint. |
| `tasks` | `table` | Default task list: `{ title, description, current, target }`. Replaced by stored progress when the player has some. |

```lua title="config.lua"
Config.JobDetails.taxi = {
    subtitle = 'Drive the city',
    rating = 2,
    requirement = 'Driver license',
    location = { x = 903.3, y = -170.5 },
    description = 'Pick up passengers at the depot and drive them anywhere in Los Santos.',
}
```

<Callout type="info">
The default config describes `delivery` and `garbage`, which are not part of the official addons. Their cards only appear if your database has jobs with those names.
</Callout>

## Exports [#exports]

### UpdateJobProgress (server) [#updatejobprogress-server]

Stores stats and tasks for a job in the player's metadata (`joblisting` key) and pushes the updated profile to the client.

```lua
exports.esx_joblisting:UpdateJobProgress(playerId, jobName, data)
```

| Name | Type | Description |
| --- | --- | --- |
| `playerId` | `number` | Server ID. |
| `jobName` | `string` | Job the progress belongs to. |
| `data.stats` | `table?` | Up to 64 key/value pairs (numbers or strings up to 64 characters). `stats.extra` may hold up to 3 `{ label, value }` entries. |
| `data.tasks` | `table?` | Up to 64 `{ title, description?, current, target }` entries. `current` and `target` are integers from 0 to 100000. |

Returns `true` on success, `false` when the player is not loaded or the arguments have the wrong type. Invalid entries are dropped silently.

```lua title="server/main.lua"
local function rewardRock(playerId, mined)
    exports.esx_joblisting:UpdateJobProgress(playerId, 'miner', {
        stats = { rocks = mined },
        tasks = {
            { title = 'Extract mineral rock', description = 'Break rocks from active quarry veins.', current = mined, target = 40 },
        },
    })
end
```

### ShowTasks (client) [#showtasks-client]

```lua
exports.esx_joblisting:ShowTasks(tasks, grade)
```

| Name | Type | Description |
| --- | --- | --- |
| `tasks` | `table` | List of `{ title, description?, current, target }`. |
| `grade` | `string?` | Subtitle shown in the HUD, usually the grade label. |

### HideTasks (client) [#hidetasks-client]

```lua
exports.esx_joblisting:HideTasks()
```

### ShowTaskProgress (client) [#showtaskprogress-client]

Shows a toast with a progress bar for 5 seconds.

```lua
exports.esx_joblisting:ShowTaskProgress({ title = 'Deliver processed sacks', current = 3, target = 10 })
```

## Server callbacks [#server-callbacks]

| Callback | Arguments | Returns |
| --- | --- | --- |
| `esx_joblisting:open` | none | `{ ok, jobs, profile }`. `ok` is `false` outside a job center. Rate limited. |
| `esx_joblisting:apply` | `jobName` | `{ ok, message?, profile? }`. |
| `esx_joblisting:quit` | none | Sets `Config.UnemployedJob`. |
| `esx_joblisting:getJobsList` | none | List of available jobs. Rate limited. |

## Events [#events]

| Event | Side | Description |
| --- | --- | --- |
| `esx_joblisting:setJob` | server (net) | `(jobName)` legacy entry point. Runs the same checks as `apply`. |
| `esx_joblisting:profileUpdated` | client | `(profile)` sent by `UpdateJobProgress`, refreshes the open UI. |

<Callout type="warn" title="Security">
`esx_joblisting:apply` and `esx_joblisting:setJob` change a player's job, so they are classic targets. The server checks distance to a job center, rejects whitelisted or missing jobs, only sets grade `0`, and applies a 1.5 second cooldown. Keep jobs that need vetting marked `whitelisted = 1`, or a cheater can still join them by standing at the job center.
</Callout>

## Common issues [#common-issues]

- **Police or EMS show up in the list.** Their `whitelisted` value is `0`. See the installation steps.
- **A job is missing.** It needs `whitelisted = 0` and a grade `0` row in `job_grades`.
- **Menu opens and closes immediately with an error.** The server did not confirm you are near a zone. Make sure `Config.Zones` matches the marker you are standing on, and do not move `Config.DrawDistance` below the marker size.
- **Card has no picture.** Add `image` in `Config.JobDetails` and put the file in `web/assets/`. It is covered by the `web/assets/*` files entry.

## Related [#related]

- [esx_jobs](/docs/addons/esx_jobs)
- [esx_whitelist](/docs/core/esx_whitelist)
- [Create a job](/docs/guides/create-a-job)
- [Database schema](/docs/guides/database-schema)
