> ## Documentation Index
> Fetch the complete documentation index at: https://firespark.cloud/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Floor plans

> Floors, structure, and tables for each store, defined as validated JSON.

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.

| Field | Type | Rules |
| - | - | - |
| `id` | string | Unique per store. Can't change after the first save |
| `name` | string | Required |
| `level` | integer | From -10 to 100 |
| `plan.version` | `1` | Schema version |
| `plan.width` | number | From 1 to 500 units |
| `plan.depth` | number | From 1 to 500 units |
| `plan.elements` | array | Structure, up to 500 items |
| `plan.tables` | array | Tables, up to 300 per floor |

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

```json theme={null}
{
  "id": "ground",
  "name": "Ground floor",
  "level": 0,
  "plan": {
    "version": 1,
    "width": 20,
    "depth": 14,
    "elements": [
      { "id": "wall-n", "kind": "wall", "x": 0, "y": 0, "width": 20, "depth": 0.2, "rotation": 0 },
      { "id": "wall-e", "kind": "wall", "x": 11.9, "y": 0.1, "width": 8.1, "depth": 13.9, "rotation": 0, "thickness": 0.2, "points": [{ "x": 19.9, "y": 0.2 }, { "x": 19.9, "y": 13.9 }, { "x": 12, "y": 13.9 }] },
      { "id": "door-1", "kind": "door", "label": "Entrance", "x": 8, "y": 13.8, "width": 2, "depth": 0.2, "rotation": 0 },
      { "id": "counter-1", "kind": "counter", "label": "Checkout", "x": 1, "y": 1, "width": 6, "depth": 1.2, "rotation": 0 },
      { "id": "kiosk-1", "kind": "kiosk", "x": 10.5, "y": 12, "width": 1, "depth": 1, "rotation": 0 },
      { "id": "zone-1", "kind": "zone", "label": "Terrace", "x": 14, "y": 1, "width": 5.5, "depth": 6.5, "rotation": 0 }
    ],
    "tables": [
      { "id": "t1", "label": "1", "seats": 2, "shape": "square", "x": 1.5, "y": 4, "width": 1, "depth": 1, "rotation": 0 },
      { "id": "t4", "label": "4", "seats": 4, "shape": "round", "x": 1.5, "y": 7.3, "width": 1.4, "depth": 1.4, "rotation": 0 },
      { "id": "t6", "label": "6", "seats": 6, "shape": "rect", "x": 1.5, "y": 10.6, "width": 2.4, "depth": 1.2, "rotation": 0 }
    ]
  }
}
```

## 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.

## Related concepts

> [card] [Stores](https://firespark.cloud/docs/concepts/stores.md)
>
> Locations that own floor plans

> [card] [Orders](https://firespark.cloud/docs/concepts/orders.md)
>
> Orders opened for seated groups


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.