Skip to main content

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.

SDK conventions

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

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}`);

Create, get, list, update, delete

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');

Batch create categories

Up to 50 categories per request.

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 },
]);

A menu item requires at least one variant. In TypeScript, create variants inline; in Python, set the item's price directly.

Create an item

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`);

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',
price: 10.99,
isAvailable: true,
});

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

Batch create items

Up to 100 items per request.

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 }],
},
]);

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');

Create an order

In TypeScript each order item references a variantId; in Python each references a menu_item_id.

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 },
});

Read orders

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}`));

Update status

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}`);

Cancel

const cancelled = await client.menuOrders.cancel('order_123', {
cancelReason: '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 lifecyclepending → confirmed → preparing → ready → completed (delivery adds out_for_delivery); record a cancelReason when cancelling.
  • Compute pricing carefullysubtotal and total are required; add tax, tip, deliveryFee, and currency as needed.

Next steps


← Back to Guides