> ## Documentation Index
> Fetch the complete documentation index at: https://docs.visual-layer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Metadata

> Developer guide for importing custom metadata using user-defined fields to enhance dataset search and filtering.

<Card title="How this Helps" icon="lightbulb">
  Add custom metadata like temperature, timestamps, and tools to images via the API. This helps you improve dataset filtering and searchability.
</Card>

This guide explains how to import custom metadata to your datasets via the API. Each metadata field is user-defined and supports one of several data types.

Custom metadata is associated at the image level and is immutable once uploaded.

To import custom metadata in Visual Layer, you must:

* Declare a metadata field (name + type).
* Upload a JSON file containing values for that field.

<Tip>
  All API requests in the Cloud environment require [authentication using a valid API token](/api-reference/authentication).
</Tip>

Visual Layer supports these value types:

* `string`.
* `float`.
* `datetime` (UTC format).
* `enum` (single or multi-select) - each enum option is limited to 20 characters.
* `link` (URL).

<Note>
  Each upload **task is used to add one metadata field only**.
  If you want to add multiple fields, you’ll need to create a separate task for each - and **make sure each field has a unique field\_name**.
</Note>

***

## Workflow Overview

The custom metadata import process follows these stages:

<Steps>
  <Step title="Ensure dataset is ready">
    Verify your dataset is in a `READY` state before beginning the upload process.
  </Step>

  <Step title="Prepare your JSON file">
    Create a JSON file mapping media IDs to metadata values as described below.
  </Step>

  <Step title="Declare the metadata field">
    Use the API to declare a new metadata attribute and receive a `task_id`.
  </Step>

  <Step title="Upload metadata values">
    Submit your JSON file containing the metadata values for the declared field.
  </Step>

  <Step title="Monitor upload progress">
    Track ingestion progress via the status endpoint and review any errors.
  </Step>

  <Step title="Access metadata in Visual Layer">
    Once uploaded, metadata becomes available for filtering and retrieval in the platform.
  </Step>
</Steps>

## Preparing the JSON File

To upload custom metadata, you'll need to associate values with the correct media items in your dataset.

Each entry must include:

* A `media_id` – a unique identifier generated by Visual Layer for each image
* A `value` – the metadata value for that image

### How to Get `media_id`s:

1. **[Export your dataset](/docs/advanced-dataset-management/export-dataset)** from the Visual Layer platform
2. The exported JSON file will include all image-level metadata, including the `media_id` assigned to each media item
3. Use these `media_id`s to match your custom metadata with the corresponding images

Your custom metadata JSON file should follow this structure:

```json theme={"theme":"monokai"}
[
  { "media_id": "f3e3d00c-1a8e-4a58-85dc-cdea7a8365a0", "value": "Sunny" },
  { "media_id": "b1ac12e7-53dc-4c7c-b93d-1836b4aa4f95", "value": "Cloudy" }
]
```

### Automation Option

For users with CSV metadata files, we provide an example automation script that handles the complete upload workflow:

<Card title="Custom Metadata Upload Script" icon="file-code-2" href="/docs/self-hosting/useful-scripts">
  Skip the manual JSON creation process with our Python script that automatically maps CSV data to Visual Layer's media\_ids and handles the API workflow. View the complete script in Useful Scripts.
</Card>

***

## Declare a New Metadata Field

Each declaration creates a new upload task for a **single field**. You can repeat the process to add more fields—just ensure each `field_name` is unique within the dataset.

```http theme={"theme":"monokai"}
POST /api/v1/datasets/{dataset_id}/custom_metadata/tasks
```

## Request Body

### For `string`, `float`, `datetime`, `link`

```json theme={"theme":"monokai"}
{
  "field_name": "Your_field_name",
  "field_type": "string" // or - float, datetime, link
}
```

### For `enum`

**`is_multi = false`**

```json theme={"theme":"monokai"}
{
  "field_name": "priority",
  "field_type": "enum",
  "enum_options": ["Low", "Medium", "High"],
  "is_multi": false
}
```

**`is_multi = true`**

```json theme={"theme":"monokai"}
{
  "field_name": "multi_colors",
  "field_type": "enum",
  "enum_options": [
    "red", "green", "blue", "yellow", "purple"
  ],
  "is_multi": true
}
```

The `is_multi` flag determines whether each image can have one or multiple values from the enum options.

* `is_multi: false` (Each image can have only one value.)
* `is_multi: true` (Each image can have multiple values.)

## Response Body:

```json theme={"theme":"monokai"}
{
  "task_id": "1234e567-e89b-12d3-a456-426614174000",
  "status": "INIT"
}
```

<Tip>
  Make sure to save the `task_id`- you’ll need it for the next steps in the flow.
</Tip>

***

## Upload Metadata File

Use the received `task_id` to upload your metadata file. Each entry must include a valid `media_id` and a `value` matching the declared field type.

```http theme={"theme":"monokai"}
POST /api/v1/datasets/{dataset_id}/custom_metadata/tasks/{task_id}
```

## Upload Format

### For `string`, `float`, `datetime`, `link`

```json theme={"theme":"monokai"}
[
  { "media_id": "f3e3d00c-1a8e-4a58-85dc-cdea7a8365a0", "value": 23.5 },
  { "media_id": "b1ac12e7-53dc-4c7c-b93d-1836b4aa4f95", "value": 24.0 }
]
```

### For `enum` - when `is_multi = false`

```json theme={"theme":"monokai"}
[
  {
    "media_id": "2283963d-6c78-442e-9606-352ab1c30e64",
    "value": "yellow"
  }
]
```

### For `enum` - when `is_multi = true`

```json theme={"theme":"monokai"}
[
  {
    "media_id": "346ba239-5422-475c-aba3-afffc984c13a",
    "value": [
      "black",
      "yellow",
      "blue",
      "pink"
    ]
  }
]
```

***

## Check Upload Status

Track progress and get visibility into errors (e.g., type mismatches).

```http theme={"theme":"monokai"}
GET /api/v1/datasets/{dataset_id}/custom_metadata/tasks/{task_id}/status
```

### Response Example:

```json theme={"theme":"monokai"}
{
  "task_id": "...",
  "status": "COMPLETED",
  "progress": 100.0,
  "inserted_rows": 6535,
  "error_count": 0,
  "sample_errors": []
}
```

### Error Response Example

```json theme={"theme":"monokai"}
{
  "task_id": "...",
  "status": "COMPLETED_WITH_ERRORS",
  "progress": 100.0,
  "inserted_rows": 6535,
  "error_count": 594,
  "sample_errors": [
    { "reason": "Expected String", "row_index": 17 }
  ]
}
```

<Note>
  At this stage, uploaded metadata is immutable - meaning it cannot be edited or replaced once submitted.\
  If any errors occur during upload, only the problematic rows will fail; all valid rows will proceed successfully.\
  You can always declare a new `field_name` and reupload the data.\
  **Support for updating existing metadata will be added in the future.**
</Note>

***

## Retrieve Metadata

### List Declared Metadata Fields

```http theme={"theme":"monokai"}
GET /api/v1/datasets/{dataset_id}/custom_metadata/schema
```

Returns all declared fields and types.

### Get Metadata for an Image

```http theme={"theme":"monokai"}
GET /api/v1/datasets/{dataset_id}/image/{image_id}/custom_metadata
```

Response:

```json theme={"theme":"monokai"}
[
  {
    "field_name": "lighting",
    "field_type": "enum",
    "value": "Sunny"
  },
  {
    "field_name": "temperature",
    "field_type": "float",
    "value": 23.5
  }
]
```

***

## Search and Filter Using Custom Metadata

Once you upload custom metadata successfully, the declared `field_name` automatically appears in the **Filters** section of the UI.

You can then filter dataset images using these fields — just like any other built-in filter.

### Supported filter types:

* **String** - substring match
* **Float** — numeric comparisons (`>`, `<`, `=`)
* **Datetime** — range queries (`BETWEEN start_date AND end_date`)
* **Enum** — exact match (single or multi-select, depending on configuration)
* **Link** - substring or exact match

<Tip>
  Filtering behavior may vary depending on the field type and the selected operator.
</Tip>

## Notes

* Only supported at the **image level** (not object level—yet!).
* Metadata is **immutable** and cannot be updated or replaced.
* Each field must have a **unique name** per dataset.
