A product catalog needs an interface where a team can manage records, an API that the application can read, and a boundary that keeps unfinished products out of the public response. This walkthrough brings those three needs together in YunCMS.
We will manage three products in Studio. An anonymous catalog request will return only the two active products and only the fields we choose. SKU and stock stay on the management side, and anonymous callers cannot create or delete records.
Disclosure: YunCMS is developed and maintained by Yunsoft Software. We verified this workflow on September 30, 2026, in an isolated local installation of @yunsoft/yuncms@0.1.23 from npm, using Node.js 24 and MySQL 8.4. All products in the screenshots are demonstration data. YunCMS is in the 0.1.x pre-stable series; test the exact release and permissions you use.
Before you start
This guide assumes YunCMS is running and you can sign in to Studio as an Administrator. For the installation itself, use the getting-started guide or the Docker Compose guide with MySQL.
Our demonstration runs at http://localhost:3038. Replace that origin in the requests with your own YunCMS address; the default local port is 3008.
1. Create the product collection
Open Data Model in the Studio application rail and choose Create collection. Enter Products as the display name and products as the API / database key. Add a description that explains what the collection holds.
The display name is for people; the API key identifies the collection in URLs and integrations.
Leave Show in Content navigation enabled. Keep the recommended creation and update timestamps and user-accountability fields. YunCMS maintains those system fields when records change.
Renaming the display label does not rename the products API key. Once a frontend consumes it, treat that key as part of the integration contract.
2. Define the catalog fields
Open the collection, select Fields → Add field, and add these five fields. Set the human label, API key and appropriate field type separately.
| Display name | API key | Studio type | Setting |
|---|---|---|---|
| Product name | name |
Short text | Required |
| SKU | sku |
Short text | Required |
| Price | price |
Decimal | Required; precision 12, scale 2 |
| Stock | stock |
Integer | Required |
| Status | status |
Short text | Required; fixed default draft
|
Custom catalog fields and system-managed fields belong to the same collection model.
We use active and draft as the status values. A short-text field does not automatically enforce that pair as an enum. For a broader editorial workflow, design an appropriate permission validation or application validation rule.
Decimal stores the price with the MySQL precision and scale you selected. This example uses one price field; model the currency explicitly if your catalog supports several currencies.
3. Add two active products and one draft
Use Content → Products → New record to add these demonstration products. The draft gives us a concrete way to check the public permission filter later.
| name | sku | price | stock | status |
|---|---|---|---|---|
| Canvas Backpack | BAG-001 | 1490.00 | 18 | active |
| Ceramic Mug | MUG-001 | 320.00 | 42 | active |
| Desk Lamp | LAMP-001 | 890.00 | 7 | draft |
The Administrator can see all three records, including their SKU and stock.
Choose Sort by → Product name to sort the list by name. Editing a product in Studio does not update a separate API copy: Studio and the Items API work with the same collection data.
4. Limit Public to catalog reading
In this scenario, product names and prices are intentionally public, so we use Access → Public. Do not apply that decision automatically to customer records, orders or data that should require sign-in.
Open the Read permission page for products. Enable Allow this action. Turn off Allow all fields and select only id, name, price and status. Leave sku, stock and the system fields unselected.
Add this visual row rule: Status → Equals → active. Its JSON equivalent is:
{"status":{"_eq":"active"}}
Select Save rules. Keep Public Create, Update and Delete permissions off.
The backend permission rule defines both readable fields and readable records.
Showing a collection in Content navigation does not grant public access. The permission filter also applies to API requests; a caller-supplied filter cannot expand the role's readable record scope.
5. Read the same catalog through REST
Because the collection key is products, its read URL is /items/products. We did not write a separate product-listing endpoint for this example.
curl --get 'http://localhost:3038/items/products' \
--data-urlencode 'fields=name,price,status' \
--data-urlencode 'sort=name' \
--data-urlencode 'limit=20'
This is an anonymous request with no Authorization header. Before configuring Public read access, the collection request returned HTTP 403. After saving the rule, the request returned HTTP 200 with this response:
{
"data": [
{
"name": "Canvas Backpack",
"price": "1490.00",
"status": "active"
},
{
"name": "Ceramic Mug",
"price": "320.00",
"status": "active"
}
],
"meta": {
"total_count": 2,
"limit": 20,
"offset": 0
}
}
The Desk Lamp record visible in Studio is absent. The response also omits sku and stock. Decimal prices arrive as JSON strings in this response, so handle currency formatting explicitly in your frontend.
You can narrow the query within the permitted scope. For example, select active products priced at least 500:
curl --get 'http://localhost:3038/items/products' \
--data-urlencode 'fields=name,price,status' \
--data-urlencode 'filter={"price":{"_gte":500}}' \
--data-urlencode 'sort=name'
That request returned only Canvas Backpack in our example. The draft priced at 890 meets the price condition but remains outside the role's row filter.
6. Connect the frontend and check the boundary
A Node.js server or server-side frontend code can consume the same endpoint:
const response = await fetch(
'http://localhost:3038/items/products?fields=name,price,status&sort=name&limit=20'
);
if (!response.ok) {
throw new Error(`Catalog request failed: ${response.status}`);
}
const { data: products, meta } = await response.json();
If browser frontend code runs on another origin, address its routing and CORS configuration separately; serving the API behind a same-origin reverse proxy is another option. This public catalog read does not require putting an Administrator token in your frontend.
Checking only as Administrator is insufficient. We also exercised the boundary with anonymous HTTP requests:
| Check | Observed result |
|---|---|
| Read before granting Public access | 403 |
| Read the selected catalog fields | 200; two active products |
| Request draft records with a caller filter | 200; empty list |
| Request disallowed sku and stock fields | 400; INVALID_QUERY |
| Create a record anonymously | 403; FORBIDDEN |
| Delete a record anonymously | 403; FORBIDDEN |
These observations apply to this catalog and permission rule. If your model introduces other roles, relations or write rules, test those flows with representative accounts too.
What can you add next?
You now have a catalog that a team manages in Studio and an application reads through a bounded API. Add product images through Files, a category relation or a separate catalog-editor role as the next requirement appears. Cart, payment, order and stock-reservation behavior are additional workflows to design.
Try the same example in your installation: create two active products and one draft, restrict the Public read rule, and compare the API response with the Administrator view. If a step fails, report the exact version and reproducible steps in the YunCMS issue tracker.
Top comments (0)