Skip to main content

Categories & Items

A menu is built from three layers. A category groups dishes for display ("Appetizers", "Pizzas"). A menu item is a single dish with its description, ingredients, allergens, and nutrition. A variant is a priced version of that dish ("Small", "Large") — and every item must have at least one, because price lives on the variant, not the item.

Category, item, and variant structureA category groups menu items one-to-many. Each item requires at least one variant, which carries the price. Categories, items, and variants each support channel mappings for delivery-platform sync.1 : N1 : N (min 1)CategorygroupingMenu itemdish · ingredients · allergensVariantcarries the priceChannel mappingsevery level maps to external IDs for DoorDash, Uber Eats, and other delivery platforms

Categories organize items into logical groups for navigation and display.

Create a category​

const category = await client.menus.createCategory({
name: 'Appetizers',
description: 'Start your meal right',
displayOrder: 1,
});

Get, list, update, delete​

const loaded = await client.menus.getCategory('cat_123');
const result = await client.menus.listCategories();

const updated = await client.menus.updateCategory({
id: 'cat_123',
name: 'Premium Appetizers',
displayOrder: 2,
});

await client.menus.deleteCategory('cat_123');

Batch create​

const result = await client.menus.createCategoryBatch([
{ name: 'Appetizers', description: 'Start your meal', displayOrder: 1 },
{ name: 'Main Courses', description: 'Signature entrees', displayOrder: 2 },
{ name: 'Desserts', description: 'Sweet treats', displayOrder: 3 },
]);
Limits

Both SDKs cap batch creation at 50 categories per request.

FieldTypeRequiredDescription
idstringautoUnique identifier
namestringYesCategory name (min 1 character)
descriptionstring | nullNoContext for the category
displayOrderinteger | nullNoDisplay order (lower = first)
channelMappingsarray | nullNoPer-channel external category IDs (channelId, externalCategoryId)
createdAt / updatedAtnumberautoTimestamps

A menu item describes a dish. In the TypeScript SDK an item is created together with its required variants; the response is the hydrated item with its variants, resolved priceRange, and modifierGroups. In the Python SDK, create_item sets the item's base price directly.

Create an item​

const pizza = await client.menus.createItem({
categoryId: 'cat_main',
name: 'Margherita Pizza',
description: 'Classic tomato and mozzarella',
price: 14.99,
isAvailable: true,
isActive: true,
preparationTime: 20,
displayOrder: 1,
variants: [
{ name: 'Small (10")', price: 14.99, isDefault: true, isActive: true, isAvailable: true },
{ name: 'Medium (12")', price: 18.99, isDefault: false, isActive: true, isAvailable: true },
{ name: 'Large (14")', price: 22.99, isDefault: false, isActive: true, isAvailable: true },
],
});

console.log(`Created ${pizza.name} with ${pizza.variants.length} sizes`);

Get, list, update, delete​

const item = await client.menus.getItem('item_123');
const result = await client.menus.listItems();

const updated = await client.menus.updateItem({
id: 'item_123',
description: 'Fresh romaine with premium Caesar dressing',
isAvailable: true,
isActive: true,
});

await client.menus.deleteItem('item_123');

Batch create​

const result = await client.menus.createItemBatch([
{
categoryId: 'cat_appetizers',
name: 'Buffalo Wings',
description: 'Crispy wings with hot sauce',
price: 11.99,
isAvailable: true,
isActive: true,
variants: [{ name: 'Default', price: 11.99, isDefault: true, isActive: true, isAvailable: true }],
},
{
categoryId: 'cat_appetizers',
name: 'Mozzarella Sticks',
description: 'Breaded and fried with marinara',
price: 8.99,
isAvailable: true,
isActive: true,
variants: [{ name: 'Default', price: 8.99, isDefault: true, isActive: true, isAvailable: true }],
},
]);
Limits

Both SDKs cap batch creation at 100 items per request.

BusinessMenuItem fields​

FieldTypeRequiredDescription
idstringautoUnique identifier
namestringYesItem name (min 1 character)
descriptionstring | nullNoDetailed description
pricenumberYesBase price (≥ 0)
categoryIdstringYesParent category (nullable on create)
ingredientsstring[] | nullNoPrimary ingredients
allergensstring[] | nullNoAllergen warnings (e.g. nuts, dairy)
nutritionalInfoobject | nullNocalories, protein, carbs, fat
isAvailablebooleanNoReal-time availability (default true)
preparationTimeinteger | nullNoEstimated prep time (minutes, > 0)
isActivebooleanNoWhether item appears in the menu (default true)
displayOrderinteger | nullNoOrder within category
dayPartsarray | nullNoRecurring availability windows (see below)
channelMappingsarray | nullNoPer-channel external item IDs
orderCount / recentOrderCount / lastOrderedAt—read-onlyServer-computed analytics

Daypart​

FieldTypeRequiredDescription
namestringYesDaypart name (e.g. "Breakfast")
startTime / endTimestringYesHH:MM window
daysOfWeekarrayNoActive days (0 = Sunday, 6 = Saturday)
Variants are required

Every menu item must have at least one variant — price lives on the variant. The TypeScript SDK creates variants inline with the item (and exposes a dedicated variants resource). The Python create_item sets the item's base price; the platform represents that as the item's variant pricing.

A variant is a size/option of an item with its own price. The TypeScript SDK manages variants as a dedicated resource for fine-grained control (add a size, change one price, mark a default). The Python menu guide prices items through the item's price and does not expose a separate variant client.

const variant = await client.menuItemVariants.create({
menuItemId: 'item_123',
name: 'Extra Large',
sku: 'PIZZA-XL-001',
price: 26.99,
isDefault: false,
displayOrder: 4,
});

const loaded = await client.menuItemVariants.get('variant_123');
const defaultVariant = await client.menuItemVariants.getDefault('item_123');

const updated = await client.menuItemVariants.update('variant_123', {
id: 'variant_123',
price: 27.99,
isAvailable: true,
});

const batch = await client.menuItemVariants.createBatch([
{ menuItemId: 'item_123', name: 'Medium', price: 18.99, isDefault: false, isActive: true, isAvailable: true },
{ menuItemId: 'item_123', name: 'Large', price: 22.99, isDefault: false, isActive: true, isAvailable: true },
]);

await client.menuItemVariants.delete('variant_123');
FieldTypeRequiredDescription
idstringautoUnique identifier
menuItemIdstringYesParent menu item
namestringYesVariant name (e.g. "Large")
descriptionstring | nullNoVariant description
pricenumberYesVariant price (≥ 0)
isAvailablebooleanNoAvailable for ordering (default true)
isActivebooleanNoAppears in the menu (default true)
isDefaultbooleanNoPre-selected default (default false)
variantChannelMappingsarray | nullNoPer-channel external variant IDs
tip

Give one variant isDefault: true for predictable UI, and use descriptive names that include size detail (e.g. Small (10") rather than just Small).

Query options​

ResourceFiltersSort fields
BusinessMenuItemsearch, locationId, categoryId, isActive, isAvailable, allergensname, price, createdAt, displayOrder
MenuItemVariantsearch, menuItemId, isActive, isAvailable, priceRangename, price, createdAt

All sorts take a direction of asc or desc, with page/pageSize pagination.