Categories
A store's classifications with filter fields, which products, booking services and resources answer
A category is a classification with filter fields, defined once per store. A fabric shop’s
fabrics category has the fields composition and weight; a fabric product carries its own answers,
such as composition silk and weight light, and storefronts filter by them. Products, booking
services and booking resources keep their answers inside themselves, in
categories: CategoryEntry[].
import type { EpochMilliseconds } from "arky-sdk";
interface Category {
id: string;
store_id: string;
key: string;
parent_id: string | null;
slugs: Record<string, string>;
blocks: Block[];
schema: CategorySchema[];
status: { type: "active" } | { type: "draft" } | { type: "archived" } | { type: "deleting" };
created_at: EpochMilliseconds;
updated_at: EpochMilliseconds;
}
type CategorySchema =
| { type: "select_one"; id: string; key: string; label: LocalizedText; options: { key: string; label: LocalizedText }[] }
| { type: "select_many"; id: string; key: string; label: LocalizedText; options: { key: string; label: LocalizedText }[] }
| { type: "number"; id: string; key: string; label: LocalizedText; min: number | null; max: number | null }
| { type: "boolean"; id: string; key: string; label: LocalizedText }
| { type: "geo_location"; id: string; key: string; label: LocalizedText };
interface CategoryEntry {
category_id: string;
fields: (
| { type: "select_one"; field_id: string; option_key: string }
| { type: "select_many"; field_id: string; option_keys: string[] }
| { type: "number"; field_id: string; value: number }
| { type: "boolean"; field_id: string; value: boolean }
| { type: "geo_location"; field_id: string; value: { lat: number; lon: number } }
)[];
}
slugs and blocks give the category its URL, name, description and picture per language.
Options have a key and a label per language; owners store the option keys, so renaming a label
never leaves an answer stale.
Admin
/v1/stores/{store_id}/categories sdk.category.create() /v1/stores/{store_id}/categories sdk.category.find() /v1/stores/{store_id}/categories/{id} sdk.category.get() /v1/stores/{store_id}/categories/{id}/children sdk.category.findChildren() /v1/stores/{store_id}/categories/{id} sdk.category.update() /v1/stores/{store_id}/categories/{id}?expected_updated_at= sdk.category.delete() const fabrics = await sdk.category.create({
store_id: storeId,
id: crypto.randomUUID(),
key: "fabrics",
parent_id: null,
slugs: { en: "fabrics", de: "stoffe" },
blocks: [
{ id: crypto.randomUUID(), key: "name", type: "localized_text", value: { en: "Fabrics", de: "Stoffe" } },
],
schema: [
{
type: "select_one",
id: crypto.randomUUID(),
key: "composition",
label: { en: "Composition", de: "Zusammensetzung" },
options: [
{ key: "silk", label: { en: "Silk", de: "Seide" } },
{ key: "linen", label: { en: "Linen", de: "Leinen" } },
],
},
{ type: "number", id: crypto.randomUUID(), key: "weight", label: { en: "Weight (g/m²)" }, min: 0, max: null },
],
});
A product answers with an entry:
const entry: CategoryEntry = {
category_id: fabrics.id,
fields: [
{ type: "select_one", field_id: compositionFieldId, option_key: "silk" },
{ type: "number", field_id: weightFieldId, value: 90 },
],
};
- An owner holds at most one entry per category, of an
activecategory of the same store. Each answer names its schema field byfield_idand matches its type; aselect_oneholds one option key and aselect_manyone or more. An owner save that leaves outcategorieskeeps its entries. - The key is unique in the store (
409 CATEGORY.KEY_TAKEN) and the slugs per store and language (409 CATEGORY.SLUG_TAKEN). A parent is a category of the same store that isn’t archived, and the hierarchy has no cycles. - Option keys are unique within a field.
- New stores start with no categories.
Changing A Category
A field keeps its type for good: changing the type under the same field id is refused. Its key, label, options and bounds may change, and fields may be added or removed. Answers that no longer fit, such as an option that was removed or a number now out of bounds, stay on their owner: Admin flags them, storefront reads and filters leave them out, and the owner’s next save that changes anything but its status is refused until they fit.
Deleting
Deleting is refused while the category has child categories (409 CATEGORY.HAS_CHILDREN), or while
anything but products, booking services, booking resources and promotions points at it
(409 REFERENCE.STILL_REFERENCED). Otherwise the category becomes
deleting, Arky removes its entries from those owners and its id from promotion targets in batches,
and then removes the record. While it is deleting, nothing new can point at it.
Webhooks post category.created, category.updated and category.deleted.
Storefront
/v1/storefront/categories/{id_or_slug} arky.category.get() /v1/storefront/categories/by-key/{key} arky.category.getByKey() /v1/storefront/categories/{id}/children arky.category.findChildren() const category = await arky.category.getByKey({ key: "fabrics" });
const bySlug = await arky.category.get({ slug: "fabrics" });
const children = await arky.category.findChildren({ id: category.id, limit: 50 });
const silk = await arky.eshop.product.find({
catalog_id: catalog.id,
category_query: [
{ category_id: category.id, query: [{ type: "select", key: "composition", option_keys: ["silk"] }] },
],
});On the storefront, get takes an id or a slug, and reads a slug in the client’s language
(X-Arky-Locale) only: a miss answers 404, and is never retried as a key. getByKey reads a
category by its key, in any language. Products and booking services are read the same way.
Filtering happens on the owners’ lists, such as products, with a category_query. A filter names
a field by its key; Arky matches the answers by that field’s id.