Plan format
An outside assistant can write a short JSON plan. The app itself has no AI. Lengths are millimetres. x increases to the right, and y increases downward. A plan needs walls or room polygons. The full floorplanner.plan version 1 file is also accepted.
Schema: https://openfloorplan.app/plan-format.schema.json (/plan-format.schema.json).
Minimal example
One room, given as a polygon.
{
"name": "One room",
"rooms": [
{
"name": "Room",
"polygon": [
[0,0],
[4000,0],
[4000,3000],
[0,3000]
]
}
]
}
Three-bedroom example
Walls, named rooms, doors, windows, and a few pieces of furniture.
{
"name": "3 bed",
"walls": [
{
"a": [0,0],
"b": [10000,0]
},
{
"a": [10000,0],
"b": [10000,8000]
},
{
"a": [10000,8000],
"b": [0,8000]
},
{
"a": [0,8000],
"b": [0,0]
},
{
"a": [0,4000],
"b": [10000,4000]
},
{
"a": [3000,0],
"b": [3000,4000]
},
{
"a": [6000,0],
"b": [6000,8000]
}
],
"rooms": [
{
"name": "Bedroom 1",
"at": [1500,2000]
},
{
"name": "Bedroom 2",
"at": [4500,2000]
},
{
"name": "Bathroom",
"at": [8000,2000]
},
{
"name": "Living room",
"at": [3000,6000]
},
{
"name": "Bedroom 3",
"at": [8000,6000]
}
],
"doors": [
{
"wall": 2,
"center": 7000
},
{
"wall": 4,
"center": 1500
},
{
"wall": 4,
"center": 4500
},
{
"wall": 6,
"center": 2000
},
{
"wall": 6,
"center": 6000
}
],
"windows": [
{
"wall": 0,
"center": 4500
},
{
"wall": 1,
"center": 2000
},
{
"wall": 3,
"center": 2000,
"type": "bay",
"width": 2400,
"depth": 600,
"angle": 45
}
],
"furniture": [
{
"id": "double-bed",
"x": 1500,
"y": 2000
},
{
"id": "double-bed",
"x": 4500,
"y": 2000
},
{
"id": "king-bed",
"x": 8000,
"y": 6000
},
{
"id": "sofa",
"x": 2000,
"y": 6000
}
]
}
Fields
name, units, and ceilingHeight
name is optional. units is "metric" or "imperial". Omit it for metric. ceilingHeight is millimetres, from 1800 to 6000. Omit it for 2400.
walls
Walls are centre lines, 100 mm thick by default. Each wall is { a, b }. A point is [x, y] or {x, y}. thickness is optional, in millimetres. Omit it for 100.
rooms
A room is either { name?, polygon } with 3 or more points, or { name?, at } with a point inside the walls. Closed walls can derive rooms. Give walls or rooms, or both.
Room areas are measured to the inner faces, so a 4000 × 3000 mm centre-line room shows 3.9 m × 2.9 m = 11.31 m². There is no separate inside-dimensions option. To get an exact inside size, offset the centre lines outward by half the wall thickness. For a 100 mm wall that is 50 mm each side, so 4100 × 3100 centre lines give a 4000 × 3000 inside room.
doors
wall is an index from 0, or a wall id. center is millimetres from wall end a. Omit it for the middle. type is single, double, or sliding. The default is single. Width defaults are 800 for single, 1500 for double, and 1800 for sliding. Height defaults to 2000. hinge is left or right, and the default is left. swing is in or out, and the default is in.
windows
Windows use the same wall and center. type is standard or bay. The default is standard. A standard window defaults to width 1200, height 1200, and sill 900. A bay defaults to width 2400, depth 600, and angle 45. angle is 45 (splayed) or 90 (box). side is in or out, and it is optional. depth, angle, and side are only for type bay.
furniture
id is a catalogue id from the table below. x and y are the centre of the piece, in millimetres. They are optional. Give both, or neither. Neither uses the middle of the first room. rotation is degrees, clockwise. The default is 0. width, depth, and height are optional, in millimetres. height is the vertical size. Omit them to use the catalogue size.
Furniture catalogue
Use these ids. Do not invent one.
| Id | Name | Width (mm) | Depth (mm) | Height (mm) |
|---|---|---|---|---|
| single-bed | Single bed | 900 | 1900 | 600 |
| double-bed | Double bed | 1350 | 1900 | 600 |
| king-bed | King bed | 1500 | 2000 | 600 |
| sofa | Sofa | 2000 | 900 | 450 |
| armchair | Armchair | 850 | 850 | 450 |
| dining-table | Dining table | 1600 | 900 | 750 |
| dining-chair | Dining chair | 450 | 500 | 900 |
| coffee-table | Coffee table | 1000 | 500 | 400 |
| desk | Desk | 1200 | 600 | 750 |
| wardrobe | Wardrobe | 1000 | 600 | 2100 |
| fridge | Fridge | 600 | 650 | 1800 |
| toilet | Toilet | 380 | 700 | 780 |
| bath | Bath | 700 | 1700 | 560 |
| basin | Basin | 500 | 400 | 850 |
| wall-mounted-tv | Wall-mounted TV | 1230 | 60 | 690 |
| tv-on-stand | TV on stand | 1230 | 250 | 1050 |
| tv-unit | TV unit | 1600 | 400 | 500 |
| bookcase | Bookcase | 800 | 300 | 1800 |
| sideboard | Sideboard | 1600 | 450 | 800 |
| 2-seat-sofa | 2-seat sofa | 1500 | 900 | 850 |
| ottoman | Ottoman | 600 | 600 | 450 |
| rug | Rug | 2000 | 1400 | 10 |
| floor-lamp | Floor lamp | 300 | 300 | 1600 |
| plant | Plant | 400 | 400 | 1200 |
| fridge-freezer | Fridge-freezer | 600 | 650 | 1850 |
| dishwasher | Dishwasher | 600 | 600 | 850 |
| washing-machine | Washing machine | 600 | 600 | 850 |
| tumble-dryer | Tumble dryer | 600 | 600 | 850 |
| range-cooker | Range cooker | 900 | 600 | 900 |
| kitchen-island | Kitchen island | 1800 | 900 | 900 |
| bar-stool | Bar stool | 400 | 400 | 750 |
| chest-of-drawers | Chest of drawers | 800 | 450 | 800 |
| bedside-table | Bedside table | 450 | 400 | 550 |
| cot-bed | Cot bed | 1440 | 740 | 900 |
| desk-chair | Desk chair | 600 | 600 | 1000 |
| filing-cabinet | Filing cabinet | 470 | 620 | 1320 |
| radiator | Radiator | 1000 | 100 | 600 |
| shower-tray | Shower tray | 900 | 900 | 1900 |
| upright-piano | Upright piano | 1500 | 600 | 1200 |
| garden-table | Garden table | 1500 | 900 | 750 |
| custom | Custom | 1000 | 1000 |
How to make a link
For a short plan, use #plan=json: plus encodeURIComponent of the JSON.
For a longer plan, use #plan=z:. That is deflate-raw bytes, then base64url. Use - and _, and strip =. The opener also accepts zlib deflate, but this snippet uses CompressionStream with deflate-raw.
Keep the link under about 8,000 characters. Count the full URL, including https://openfloorplan.app/, for both json: and z:. A json: link is used when https://openfloorplan.app/#plan=json: plus the encoded JSON is at most 8,000 characters. Otherwise use z:, counted the same way.
If the compressed link is still over about 8 kB, do not use a link. Give the person a .floorplan.json file in the full floorplanner.plan version 1 format (the schema's second form). They import that file with Import. The short JSON is for links and for Paste plan. File import does not accept the short JSON.
async function floorPlanLink(plan) {
const json = JSON.stringify(plan)
const jsonUrl = 'https://openfloorplan.app/#plan=json:' + encodeURIComponent(json)
if (jsonUrl.length <= 8000) return jsonUrl
const compressed = new Response(
new Blob([new TextEncoder().encode(json)]).stream().pipeThrough(new CompressionStream('deflate-raw')),
)
const bytes = new Uint8Array(await compressed.arrayBuffer())
let binary = ''
for (const byte of bytes) binary += String.fromCharCode(byte)
const b64 = btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')
const zUrl = 'https://openfloorplan.app/#plan=z:' + b64
if (zUrl.length <= 8000) return zUrl
return null
}
A null result means give them a .floorplan.json file instead.
From a photo of an estate agent's plan
The JSON cannot hold the photo. The person can import the picture in the app. Use a JPG, PNG, WebP, or SVG. A PDF is not imported.
Read the printed lengths. Convert metres to millimetres. 4.00 m is 4000.
Draw each wall as a segment from a to b. Put the top-left of the picture near [0, 0]. y increases downward.
Put doors and windows on the wall. Measure center from that wall's a end.
A bay window is type "bay", with width, depth, and angle 45 or 90.
Name each room with at (a point inside that room) or with a polygon.
Do not invent a length that is not printed.