Menu Management Guide
Manage restaurant menus, item variants, modifiers, and food-service orders.
This guide covers menu categories and items, size/price variants, customization modifiers, and the order lifecycle.
TypeScript examples follow wiil-js (enums import from wiil-core-js); Python examples follow wiil. A menu item requires at least one variant for pricing — in TypeScript you create variants inline and order items reference a variantId; in Python items are priced directly and order items reference a menu_item_id. Variants and modifiers are exposed through the TypeScript SDK; those sections are labeled.
Quick start
- TypeScript
- Python
import { WiilClient } from 'wiil-js';
import { MenuOrderType } from 'wiil-core-js';
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });
// 1. Create a category
const category = await client.menus.createCategory({
name: 'Main Courses',
description: 'Signature entrees and main dishes',
displayOrder: 1,
});
// 2. Create an item with its required variants
const item = await client.menus.createItem({
categoryId: category.id,
name: 'Cheeseburger',
description: 'Angus beef with aged cheddar',
price: 12.99,
isAvailable: true,
isActive: true,
preparationTime: 15,
variants: [
{ name: 'Default', price: 12.99, isDefault: true, isActive: true, isAvailable: true },
],
});
// 3. Place an order (items require variantId)
const order = await client.menuOrders.create({
customerId: 'cust_123',
type: MenuOrderType.TAKEOUT,
orderDate: Date.now(),
items: [
{
menuItemId: item.id,
variantId: item.variants[0].id,
itemName: item.name,
quantity: 2,
unitPrice: 12.99,
totalPrice: 25.98,
},
],
pricing: { subtotal: 25.98, total: 25.98 },
});
console.log(`Order Created: ${order.id}`);
import os
from time import time
from wiil import WiilClient
from wiil.models.business_mgt import (
CreateBusinessMenuItem,
CreateMenuCategory,
CreateMenuOrder,
MenuOrderItemBase,
OrderPricing,
)
client = WiilClient(api_key=os.environ["WIIL_API_KEY"])
now_ms = int(time() * 1000)
category = client.menus.create_category(
CreateMenuCategory(name="Main Courses", description="Signature entrees", display_order=1)
)
menu_item = client.menus.create_item(
CreateBusinessMenuItem(
name="Cheeseburger",
description="Angus beef with aged cheddar",
price=12.99,
category_id=category.id,
ingredients=["beef", "cheese", "lettuce", "tomato", "bun"],
allergens=["gluten", "dairy"],
is_available=True,
preparation_time=15,
is_active=True,
)
)
order = client.menu_orders.create(
CreateMenuOrder(
type="takeout",
items=[
MenuOrderItemBase(
menu_item_id=menu_item.id,
item_name=menu_item.name,
quantity=2,
unit_price=menu_item.price,
total_price=menu_item.price * 2,
)
],
customer_id="cust_123",
pricing=OrderPricing(subtotal=25.98, tax=2.60, tip=5.00, total=33.58, currency="USD"),
order_date=now_ms,
source="web",
)
)
print("Created menu item:", menu_item.id)
print("Created menu order:", order.id)
Menu categories
Create, get, list, update, delete
- TypeScript
- Python
const category = await client.menus.createCategory({
name: 'Appetizers',
description: 'Start your meal right',
displayOrder: 1,
});
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');
from wiil.models.business_mgt import CreateMenuCategory, UpdateMenuCategory
category = client.menus.create_category(
CreateMenuCategory(name="Appetizers", description="Start your meal right", display_order=1)
)
loaded = client.menus.get_category(category.id)
categories = client.menus.list_categories()
updated = client.menus.update_category(
UpdateMenuCategory(id=category.id, name="Premium Appetizers", display_order=2)
)
client.menus.delete_category(updated.id)
Batch create categories
Up to 50 categories per request.
- TypeScript
- Python
const categories = 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 },
]);
from wiil.models.business_mgt import CreateMenuCategory
categories = client.menus.create_category_batch([
CreateMenuCategory(name="Appetizers", display_order=1),
CreateMenuCategory(name="Main Courses", display_order=2),
CreateMenuCategory(name="Desserts", display_order=3),
])
Menu items
A menu item requires at least one variant. In TypeScript, create variants inline; in Python, set the item's price directly.
Create an item
- TypeScript
- Python
const item = 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 ${item.name} with ${item.variants.length} sizes`);
from wiil.models.business_mgt import CreateBusinessMenuItem
item = client.menus.create_item(
CreateBusinessMenuItem(
name="Caesar Salad",
description="Fresh romaine with house-made dressing",
price=9.99,
category_id="cat_123",
ingredients=["romaine", "parmesan", "croutons"],
allergens=["dairy", "gluten", "eggs"],
preparation_time=10,
is_available=True,
)
)
Get, list, update, delete
- TypeScript
- Python
const item = await client.menus.getItem('item_123');
const result = await client.menus.listItems();
const updated = await client.menus.updateItem({
id: 'item_123',
price: 10.99,
isAvailable: true,
});
await client.menus.deleteItem('item_123');
from wiil.models.business_mgt import UpdateBusinessMenuItem
from wiil.types import PaginationRequest
fetched = client.menus.get_item("item_123")
all_items = client.menus.list_items(PaginationRequest(page=1, page_size=50), include_deleted=False)
by_category = client.menus.get_items_by_category("cat_123", include_unavailable=False)
popular = client.menus.get_popular_items(limit=10)
updated = client.menus.update_item(
UpdateBusinessMenuItem(id="item_123", price=10.99, is_available=True)
)
client.menus.delete_item("item_123")
Batch create items
Up to 100 items per request.
- TypeScript
- Python
const items = 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 }],
},
]);
from wiil.models.business_mgt import CreateBusinessMenuItem
items = client.menus.create_item_batch([
CreateBusinessMenuItem(name="Caesar Salad", description="Fresh romaine", price=12.00, category_id="cat_appetizers", is_available=True),
CreateBusinessMenuItem(name="Grilled Salmon", description="Atlantic salmon", price=28.00, category_id="cat_main", is_available=True),
CreateBusinessMenuItem(name="Chocolate Cake", description="Rich layer cake", price=9.00, category_id="cat_desserts", is_available=True),
])
Menu item variants
The TypeScript SDK manages size/price variations as a dedicated resource. (TypeScript SDK)
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');
Modifiers
Let customers customize items with option groups, then bind groups to items. (TypeScript SDK)
// Create a modifier group with options
const toppings = await client.modifiers.createGroup({
name: 'Toppings',
description: 'Add extra toppings',
isRequired: false,
minSelection: 0,
maxSelection: 3,
displayOrder: 1,
isActive: true,
options: [
{ name: 'Extra Cheese', priceDelta: 1.50, displayOrder: 1, isDefault: false, isActive: true },
{ name: 'Bacon', priceDelta: 2.00, displayOrder: 2, isDefault: false, isActive: true },
],
});
const group = await client.modifiers.getGroup('group_123');
const groups = await client.modifiers.listGroups();
await client.modifiers.updateGroup('group_123', { id: 'group_123', description: 'Premium toppings' });
await client.modifiers.deleteGroup('group_123');
// Manage individual options
const option = await client.modifiers.createOption({
modifierGroupId: 'group_123',
name: 'Jalapenos',
priceDelta: 0.75,
displayOrder: 3,
isDefault: false,
isActive: true,
});
const options = await client.modifiers.getOptionsByGroup('group_123');
await client.modifiers.updateOption('option_123', { id: 'option_123', priceDelta: 1.00 });
await client.modifiers.deleteOption('option_123');
// Bind a group to a menu item
const binding = await client.modifiers.createBinding({
menuItemId: 'item_burger',
modifierGroupId: 'group_toppings',
displayOrder: 1,
isRequired: false,
});
const bindings = await client.modifiers.getBindingsByMenuItem('item_burger');
await client.modifiers.updateBinding('binding_123', { id: 'binding_123', displayOrder: 2, isActive: true });
await client.modifiers.deleteBinding('binding_123');
Menu orders
Create an order
In TypeScript each order item references a variantId; in Python each references a menu_item_id.
- TypeScript
- Python
import { MenuOrderType } from 'wiil-core-js';
const order = await client.menuOrders.create({
customerId: 'cust_456',
type: MenuOrderType.DINE_IN,
orderDate: Date.now(),
items: [
{
menuItemId: 'item_123',
variantId: 'variant_456',
itemName: 'Cheeseburger',
quantity: 2,
unitPrice: 12.99,
totalPrice: 25.98,
},
],
pricing: { subtotal: 25.98, total: 25.98 },
});
from time import time
from wiil.models.business_mgt import CreateMenuOrder, MenuOrderItemBase, OrderPricing
order = client.menu_orders.create(
CreateMenuOrder(
type="delivery",
items=[
MenuOrderItemBase(
menu_item_id="item_123",
item_name="Cheeseburger",
quantity=2,
unit_price=12.99,
total_price=25.98,
special_instructions="No onions",
)
],
customer_id="cust_456",
pricing=OrderPricing(subtotal=25.98, tax=2.60, tip=5.00, total=33.58),
order_date=int(time() * 1000),
source="web",
)
)
Read orders
- TypeScript
- Python
const order = await client.menuOrders.get('order_123');
console.log(`${order.status} — $${order.pricing.total}`);
const result = await client.menuOrders.list();
result.data.forEach(o => console.log(`- ${o.id}: ${o.status}`));
from wiil.types import PaginationRequest
loaded = client.menu_orders.get("order_123")
customer_orders = client.menu_orders.get_by_customer(
"cust_456", PaginationRequest(page=1, page_size=20)
)
print(loaded.status, customer_orders.meta.total_count)
Update status
- TypeScript
- Python
import { OrderStatus } from 'wiil-core-js';
const updated = await client.menuOrders.updateStatus('order_123', {
id: 'order_123',
status: OrderStatus.PREPARING,
estimatedReadyTime: Math.floor(Date.now() / 1000) + 15 * 60, // Unix seconds
actualReadyTime: null,
});
console.log(`Status: ${updated.status}`);
updated = client.menu_orders.update_status("order_123", "preparing")
print("Status:", updated.status)
Cancel
- TypeScript
- Python
const cancelled = await client.menuOrders.cancel('order_123', {
cancelReason: 'Customer requested cancellation',
});
cancelled = client.menu_orders.cancel("order_123", reason="Customer requested cancellation")
Update and delete
Order updates and deletion are exposed through the TypeScript SDK.
const updated = await client.menuOrders.update({
id: 'order_123',
pricing: { subtotal: 35.00, total: 35.00 },
});
await client.menuOrders.delete('order_123');
Enums
import { MenuOrderType, OrderStatus } from 'wiil-core-js';
// MenuOrderType: 'dine_in' | 'takeout' | 'delivery'
// OrderStatus: 'pending' | 'confirmed' | 'preparing' | 'ready'
// | 'out_for_delivery' | 'completed' | 'cancelled' | 'returned'
The standard order lifecycle is pending → confirmed → preparing → ready → completed; delivery orders insert out_for_delivery before completed. Payment status tracks separately: pending, paid, partial, failed, refunded.
Complete example: restaurant setup
import { WiilClient } from 'wiil-js';
import { MenuOrderType, OrderStatus, PreferredContactMethod } from 'wiil-core-js';
async function setupRestaurant() {
const client = new WiilClient({ apiKey: process.env.WIIL_API_KEY! });
// 1. Customer
const customer = await client.customers.create({
phone_number: '+15551234567',
firstname: 'John',
lastname: 'Doe',
preferred_language: 'en',
preferred_contact_method: PreferredContactMethod.SMS,
isValidatedNames: false,
});
// 2. Categories
const appetizers = await client.menus.createCategory({ name: 'Appetizers', description: 'Start your meal', displayOrder: 1 });
const entrees = await client.menus.createCategory({ name: 'Entrees', description: 'Main courses', displayOrder: 2 });
// 3. Items with variants
const wings = await client.menus.createItem({
categoryId: appetizers.id,
name: 'Buffalo Wings',
description: 'Crispy wings with hot sauce',
price: 11.99,
isAvailable: true,
isActive: true,
preparationTime: 15,
variants: [
{ name: '6 pieces', price: 11.99, isDefault: true, isActive: true, isAvailable: true },
{ name: '12 pieces', price: 19.99, isDefault: false, isActive: true, isAvailable: true },
],
});
const burger = await client.menus.createItem({
categoryId: entrees.id,
name: 'Classic Burger',
description: 'Angus beef with all the fixings',
price: 14.99,
isAvailable: true,
isActive: true,
preparationTime: 12,
variants: [{ name: 'Default', price: 14.99, isDefault: true, isActive: true, isAvailable: true }],
});
// 4. Modifier group bound to the burger
const toppings = await client.modifiers.createGroup({
name: 'Extra Toppings',
isRequired: false,
minSelection: 0,
maxSelection: 3,
displayOrder: 1,
isActive: true,
options: [
{ name: 'Bacon', priceDelta: 2.00, displayOrder: 1, isDefault: false, isActive: true },
{ name: 'Extra Cheese', priceDelta: 1.50, displayOrder: 2, isDefault: false, isActive: true },
],
});
await client.modifiers.createBinding({ menuItemId: burger.id, modifierGroupId: toppings.id, displayOrder: 1, isRequired: false });
// 5. Order
const order = await client.menuOrders.create({
customerId: customer.id,
type: MenuOrderType.DINE_IN,
orderDate: Date.now(),
items: [
{ menuItemId: wings.id, variantId: wings.variants[1].id, itemName: 'Buffalo Wings (12 pieces)', quantity: 1, unitPrice: 19.99, totalPrice: 19.99 },
{ menuItemId: burger.id, variantId: burger.variants[0].id, itemName: 'Classic Burger', quantity: 2, unitPrice: 14.99, totalPrice: 29.98 },
],
pricing: { subtotal: 49.97, total: 49.97 },
});
// 6. Advance the lifecycle
await client.menuOrders.updateStatus(order.id, { id: order.id, status: OrderStatus.CONFIRMED, estimatedReadyTime: null, actualReadyTime: null });
await client.menuOrders.updateStatus(order.id, { id: order.id, status: OrderStatus.PREPARING, estimatedReadyTime: Math.floor(Date.now() / 1000) + 15 * 60, actualReadyTime: null });
await client.menuOrders.updateStatus(order.id, { id: order.id, status: OrderStatus.COMPLETED, estimatedReadyTime: null, actualReadyTime: null });
return { customer, categories: [appetizers, entrees], items: [wings, burger], order };
}
setupRestaurant().catch(console.error);
Best practices
- Always include variants (TS) / price (Python) — a menu item must be priced. In TypeScript, every item needs at least one variant; order items then reference a
variantId. - Capture names and prices at order time — the SDKs store the item name and price on each order line, so records stay accurate when the menu changes.
- Follow the status lifecycle —
pending → confirmed → preparing → ready → completed(delivery addsout_for_delivery); record acancelReasonwhen cancelling. - Compute pricing carefully —
subtotalandtotalare required; addtax,tip,deliveryFee, andcurrencyas needed.
Next steps
- Catalog reference: Categories & Items, Modifiers, Orders
- Notifications: appointment and order updates via outbound communications