Skip to main content
A floor plan is an approximate top-down layout of a store: its floors, the fixed structure on each floor (walls, counter, kiosks, doors, columns, zones), and the tables with their seats. Floor plans are optional and enabled per store. The dashboard is where you draw the plan. Table occupancy is not set here: it comes from the POS when a group is seated and its orders are opened.

Enable a floor plan

  1. Go to Stores → Floor plans.
  2. Open a store and turn on Show floor plan.
  3. Add a floor. Drag tables and structure from the palette onto the canvas, draw walls corner by corner and zones as rectangles, or paste the floor JSON with Edit JSON. Select items to move, resize, or rotate them. Every action also works from the keyboard, and Undo (Ctrl/Cmd+Z) reverts any change.
  4. Select Save. Every floor with changes is validated and saved.
A store can have several floors. Each floor has a level (0 for the ground floor, negative for basements) used to sort them.

Units and coordinates

Sizes use abstract units, where 1 unit is roughly 1 meter. The origin is the top-left corner of the floor. x grows to the right and y grows downward. Every item is a box placed at x, y with a width and depth, plus an optional rotation in degrees around its center.

Floor JSON

The same schema is used by the editor, the API, and the floor plan views. It lives in @repo/validations (vStoreFloors.floor()), so every surface validates it the same way. Every element and table has id, x, y, width, depth, and rotation (at least 0 and below 360). Items must fit inside the floor, and ids can’t repeat on the floor.
  • Elements add kind (wall, counter, kiosk, door, column, or zone) and an optional label.
  • Polyline walls are walls that also have points (2 to 200 corners along the wall’s centerline, in floor units) and an optional thickness (0.1 to 2, default 0.2). Their box is the bounding box of the drawn wall and their rotation is 0. Only walls can have points or thickness, and every corner must be inside the floor. Walls without points are still plain boxes, so older floors stay valid.
  • Tables add label (unique per floor, case-insensitive), seats (1 to 50), and shape (square, round, or rect). Table ids are unique across all floors of the store, because the POS and reservations refer to them.

Example

How it’s stored

A floor is saved as two kinds of data, on purpose:
  • Structure as JSON. Walls, counters, kiosks, doors, columns, and zones are stored as one jsonb document per floor (store_floors.layout). Nothing else refers to them, they’re always read and edited together, and saving them as a single document keeps each edit atomic.
  • Tables as rows. Each table is a row in store_tables. Tables have their own life: the POS marks them occupied, groups them, and links them to orders, and reservations will point to them. Rows give each table a stable identity, foreign keys, row-level security, and per-row realtime updates when its status changes, without rewriting the whole floor.
When you save, the API takes the floor JSON, stores the structure in the floor document, and creates, updates, or removes table rows in a single transaction.

Stores

Locations that own floor plans

Orders

Orders opened for seated groups