Create or Upsert Metafield
curl --request POST \
--url https://api.launchmystore.io/api/v1/metafields.json \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"namespace": "<string>",
"key": "<string>",
"type": "<string>",
"value": "<any>",
"ownerType": "<string>",
"ownerId": "<string>"
}
'import requests
url = "https://api.launchmystore.io/api/v1/metafields.json"
payload = {
"namespace": "<string>",
"key": "<string>",
"type": "<string>",
"value": "<any>",
"ownerType": "<string>",
"ownerId": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
namespace: '<string>',
key: '<string>',
type: '<string>',
value: '<any>',
ownerType: '<string>',
ownerId: '<string>'
})
};
fetch('https://api.launchmystore.io/api/v1/metafields.json', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/api/v1/metafields.json",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'namespace' => '<string>',
'key' => '<string>',
'type' => '<string>',
'value' => '<any>',
'ownerType' => '<string>',
'ownerId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/api/v1/metafields.json"
payload := strings.NewReader("{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.launchmystore.io/api/v1/metafields.json")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/api/v1/metafields.json")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": 123,
"state": "<string>",
"message": "<string>",
"data.metafield": {}
}Metafields
Create or Upsert Metafield
Create a new metafield, or update if one already exists for the same owner+namespace+key
POST
/
api
/
v1
/
metafields.json
Create or Upsert Metafield
curl --request POST \
--url https://api.launchmystore.io/api/v1/metafields.json \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"namespace": "<string>",
"key": "<string>",
"type": "<string>",
"value": "<any>",
"ownerType": "<string>",
"ownerId": "<string>"
}
'import requests
url = "https://api.launchmystore.io/api/v1/metafields.json"
payload = {
"namespace": "<string>",
"key": "<string>",
"type": "<string>",
"value": "<any>",
"ownerType": "<string>",
"ownerId": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
namespace: '<string>',
key: '<string>',
type: '<string>',
value: '<any>',
ownerType: '<string>',
ownerId: '<string>'
})
};
fetch('https://api.launchmystore.io/api/v1/metafields.json', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.launchmystore.io/api/v1/metafields.json",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'namespace' => '<string>',
'key' => '<string>',
'type' => '<string>',
'value' => '<any>',
'ownerType' => '<string>',
'ownerId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.launchmystore.io/api/v1/metafields.json"
payload := strings.NewReader("{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.launchmystore.io/api/v1/metafields.json")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.launchmystore.io/api/v1/metafields.json")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"namespace\": \"<string>\",\n \"key\": \"<string>\",\n \"type\": \"<string>\",\n \"value\": \"<any>\",\n \"ownerType\": \"<string>\",\n \"ownerId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": 123,
"state": "<string>",
"message": "<string>",
"data.metafield": {}
}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
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": "e5f6a7b8-9c0d-4e1f-a2b3-c4d5e6f7a8b9"
}'
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: 'e5f6a7b8-9c0d-4e1f-a2b3-c4d5e6f7a8b9',
}),
});
Required scope
write_metafields
Body parameters
string
required
Namespace. Use your app handle (e.g.
subscriptions_pro) — custom is
the merchant-managed namespace.string
required
Field key. Unique per
(ownerType, namespace, ownerId).string
The OAuth App API accepts a 21-value subset of the type catalogue:
string, integer, json, boolean, color, date, date_time,
dimension, file_reference, json_string, money,
multi_line_text_field, number_decimal, number_integer,
product_reference, rating, rich_text_field,
single_line_text_field, url, volume, weight. It has no list.*
variants, and among references only product_reference and
file_reference (not collection_reference, variant_reference,
customer_reference, or page_reference). An unsupported type is
rejected with HTTP 422. The full 22-type catalogue and list.* are
available only on the Admin API. Stored verbatim and used by the storefront
when decoding/rendering the value. If omitted, type defaults to
string.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.An empty string is rejected with 422. To represent “nothing selected”,
either delete the metafield or store an
explicit sentinel (e.g. "*" for “all”). Writing "" is the common way apps
break a “select none / select everything” toggle — the save silently fails
while the UI shows the new state.string
required
One of
shop, product, variant, collection, customer, order,
page, blog, article, cart. Lowercase only.string
required
The id of the owning resource. Required for every owner type except
shop.Examples by type
Scalar text
{
"namespace": "custom",
"key": "badge",
"type": "single_line_text_field",
"value": "BESTSELLER",
"ownerType": "product",
"ownerId": "..."
}
Number
{
"namespace": "custom",
"key": "warranty_years",
"type": "number_integer",
"value": 5,
"ownerType": "product",
"ownerId": "..."
}
Boolean
{
"namespace": "custom",
"key": "is_featured",
"type": "boolean",
"value": true,
"ownerType": "product",
"ownerId": "..."
}
Rich text (HTML, rendered unescaped in Aqua)
{
"namespace": "custom",
"key": "care_instructions",
"type": "rich_text_field",
"value": "<p>Hand wash only.</p>",
"ownerType": "product",
"ownerId": "..."
}
Measurement
{
"namespace": "specs",
"key": "weight",
"type": "weight",
"value": { "unit": "KILOGRAMS", "value": 1.2 },
"ownerType": "product",
"ownerId": "..."
}
Money
{
"namespace": "custom",
"key": "deposit",
"type": "money",
"value": { "amount": "12.50", "currency_code": "USD" },
"ownerType": "product",
"ownerId": "..."
}
Reference
The App API supports onlyproduct_reference and file_reference. Other
reference types (collection_reference, variant_reference,
customer_reference, page_reference) are Admin API only and are
rejected by the OAuth App API with 422.
{
"namespace": "custom",
"key": "matching_product",
"type": "product_reference",
"value": "8b3e2c10-...",
"ownerType": "product",
"ownerId": "..."
}
List (Admin API only)
list.* types are not supported by the OAuth App API — an App API
request with type: "list.single_line_text_field" is rejected with 422.
Lists are available only on the Admin API:
{
"namespace": "custom",
"key": "tags",
"type": "list.single_line_text_field",
"value": ["red", "limited", "sale"],
"ownerType": "product",
"ownerId": "..."
}
JSON (free-form structured data)
{
"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 underdata.metafield.
integer
201 if created, 200 if updated.string
success or error.string
Human-readable message, or
null on success.object
The created or updated metafield row.
value is returned as the stored
string form (scalars stringified, compound types JSON-encoded). See
List Metafields for shape.Example response
{
"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": "e5f6a7b8-9c0d-4e1f-a2b3-c4d5e6f7a8b9",
"appId": "your-app-id",
"createdAt": "2026-05-09T13:32:53.135Z",
"updatedAt": "2026-05-09T13:32:53.135Z"
}
},
"count": null,
"pagination": null
}
The
type name is validated against the App API enum: an unsupported
type (e.g. any list.*, collection_reference, variant_reference,
customer_reference, page_reference) is rejected with HTTP 422. Only
the value-vs-type validation is skipped on this OAuth App API path —
values are stored as provided, so it is your app’s responsibility to send a
well-formed value for the declared type. A missing required field
(namespace, key, value, ownerType, ownerId) still returns 400.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) |
422 | Unsupported type (not in the App API enum — e.g. any list.* or an unsupported reference type) |
401 | Invalid or missing access token |
403 | App doesn’t have the write_metafields scope |
500 | Unexpected server error while upserting |