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

# Create or Upsert Metafield

> Create a new metafield, or update if one already exists for the same owner+namespace+key

# Create or Upsert Metafield

This endpoint **upserts**: if a metafield with the same
`(ownerType, namespace, key, ownerId)` already exists for your app, the
value is updated; otherwise a new metafield is created. There is always
at most one metafield per (resource, namespace, key) per app.

## Request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.launchmystore.io/api/v1/metafields.json" \
    -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "namespace": "custom",
      "key": "warranty_years",
      "type": "number_integer",
      "value": 5,
      "ownerType": "product",
      "ownerId": "94995c07-e8a5-43f2-9d1a-18ccf8199adf"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.launchmystore.io/api/v1/metafields.json', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      namespace: 'custom',
      key: 'warranty_years',
      type: 'number_integer',
      value: 5,
      ownerType: 'product',
      ownerId: '94995c07-e8a5-43f2-9d1a-18ccf8199adf',
    }),
  });
  ```
</CodeGroup>

## Required scope

`write_metafields`

## Body parameters

<ParamField body="namespace" type="string" required>
  Namespace. Use your app handle (e.g. `subscriptions_pro`) — `custom` is
  the merchant-managed namespace.
</ParamField>

<ParamField body="key" type="string" required>
  Field key. Unique per `(ownerType, namespace, ownerId)`.
</ParamField>

<ParamField body="type" type="string">
  One of the [supported types](/api-reference/metafields/overview#supported-types-22).
  Stored verbatim and used by the storefront when decoding/rendering the
  value. The App API does **not** validate `value` against the `type` — it
  is your app's responsibility to send a well-formed value for the type you
  declare. If omitted, `type` defaults to `single_line_text_field`.
</ParamField>

<ParamField body="value" type="any" required>
  The value. Scalars are sent as-is (string, number, boolean). Compound
  types (`weight`, `dimension`, `volume`, `money`, `rating`, lists, JSON)
  are sent as JSON.
</ParamField>

<ParamField body="ownerType" type="string" required>
  One of `shop`, `product`, `variant`, `collection`, `customer`, `order`,
  `page`, `blog`, `article`. **Lowercase only.**
</ParamField>

<ParamField body="ownerId" type="string" required>
  The id of the owning resource. Required for every owner type *except*
  `shop`.
</ParamField>

## Examples by type

### Scalar text

```json theme={null}
{
  "namespace": "custom",
  "key": "badge",
  "type": "single_line_text_field",
  "value": "BESTSELLER",
  "ownerType": "product",
  "ownerId": "..."
}
```

### Number

```json theme={null}
{
  "namespace": "custom",
  "key": "warranty_years",
  "type": "number_integer",
  "value": 5,
  "ownerType": "product",
  "ownerId": "..."
}
```

### Boolean

```json theme={null}
{
  "namespace": "custom",
  "key": "is_featured",
  "type": "boolean",
  "value": true,
  "ownerType": "product",
  "ownerId": "..."
}
```

### Rich text (HTML, rendered unescaped in Aqua)

```json theme={null}
{
  "namespace": "custom",
  "key": "care_instructions",
  "type": "rich_text_field",
  "value": "<p>Hand wash only.</p>",
  "ownerType": "product",
  "ownerId": "..."
}
```

### Measurement

```json theme={null}
{
  "namespace": "specs",
  "key": "weight",
  "type": "weight",
  "value": { "unit": "KILOGRAMS", "value": 1.2 },
  "ownerType": "product",
  "ownerId": "..."
}
```

### Money

```json theme={null}
{
  "namespace": "custom",
  "key": "deposit",
  "type": "money",
  "value": { "amount": "12.50", "currency_code": "USD" },
  "ownerType": "product",
  "ownerId": "..."
}
```

### Reference

```json theme={null}
{
  "namespace": "custom",
  "key": "matching_collection",
  "type": "collection_reference",
  "value": "8b3e2c10-...",
  "ownerType": "product",
  "ownerId": "..."
}
```

### List

```json theme={null}
{
  "namespace": "custom",
  "key": "tags",
  "type": "list.single_line_text_field",
  "value": ["red", "limited", "sale"],
  "ownerType": "product",
  "ownerId": "..."
}
```

### JSON (free-form structured data)

```json theme={null}
{
  "namespace": "custom",
  "key": "specifications",
  "type": "json",
  "value": {
    "dimensions": { "width": 10, "height": 15, "depth": 5 },
    "weight": "0.5kg"
  },
  "ownerType": "product",
  "ownerId": "..."
}
```

## Response

The response is the standard envelope. On success the created/updated
metafield is returned under `data.metafield`.

<ResponseField name="status" type="integer">
  `201` if created, `200` if updated.
</ResponseField>

<ResponseField name="state" type="string">
  `success` or `error`.
</ResponseField>

<ResponseField name="message" type="string">
  Human-readable message, or `null` on success.
</ResponseField>

<ResponseField name="data.metafield" type="object">
  The created or updated metafield row. `value` is returned as the stored
  string form (scalars stringified, compound types JSON-encoded). See
  [List Metafields](/api-reference/metafields/list#response) for shape.
</ResponseField>

## Example response

```json theme={null}
{
  "status": 201,
  "state": "success",
  "message": null,
  "data": {
    "metafield": {
      "metafieldId": "e607f43e-9251-451e-a45c-38bf28d61154",
      "storeId": "1b2c...",
      "namespace": "custom",
      "key": "warranty_years",
      "value": "5",
      "type": "number_integer",
      "ownerType": "product",
      "ownerId": "94995c07-e8a5-43f2-9d1a-18ccf8199adf",
      "appId": "your-app-id",
      "createdAt": "2026-05-09T13:32:53.135Z",
      "updatedAt": "2026-05-09T13:32:53.135Z"
    }
  },
  "count": null,
  "pagination": null
}
```

<Note>
  This OAuth App API path performs **no** type validation — it stores the
  value as-is. Value validation against the declared type only runs on the
  merchant-managed metafields path, not here. A malformed value is accepted
  and stored; the only `400` returned is for missing required fields
  (`namespace`, `key`, `value`, `ownerType`, `ownerId`).
</Note>

## Cache invalidation

A successful upsert automatically expires the cached copy of the owning
resource and any cached page HTML that rendered it.

The next storefront render sees the new value.

## Error codes

| Status | Description                                                                       |
| ------ | --------------------------------------------------------------------------------- |
| `400`  | A required field is missing (`namespace`, `key`, `value`, `ownerType`, `ownerId`) |
| `401`  | Invalid or missing access token                                                   |
| `403`  | App doesn't have the `write_metafields` scope                                     |
| `500`  | Unexpected server error while upserting                                           |
