GeoJSON properties schema examples for floor plans
The geometry is the easy part. What makes a floor plan file usable is a predictable properties object. These examples show a consistent structure for rooms, floors, doors and desks, the keys FloorJSON exports, and a JSON Schema you can validate files against.
Five rules that save you a refactor later
- Put a stable ID on the Feature, not only in properties. GeoJSON allows a top-level
"id". Libraries such as MapLibre use it for hover and selection state. - Pick one naming style.
snake_caseis the most common in GeoJSON. Don't mixroomName,room-nameandRoom Name. - Store numbers as numbers.
"level": 1, not"level": "1", so filters and sorting work. - Put units in key names:
area_m2,height_m,capacity_people. - Use
nullfor "known to be empty" and leave the key out for "not collected". Readers can tell the difference.
Example 1: a room, before and after
The left version is typical of a hand-built file: mixed naming, numbers as strings, and the floor buried in a label. The right version holds the same information in keys you can filter on.
{
"type": "Feature",
"properties": {
"Room Name": "Meeting room 2 (1st floor)",
"roomType": "meeting",
"Area": "18.5 m2",
"seats": "8",
"color": "orange"
},
"geometry": { "type": "Polygon", "coordinates": [...] }
}{
"type": "Feature",
"id": "room-1-02",
"properties": {
"name": "Meeting room 2",
"category": "room",
"level": 1,
"level_name": "First floor",
"area_m2": 18.5,
"capacity_people": 8,
"fill": "#f0a020"
},
"geometry": { "type": "Polygon", "coordinates": [...] }
}Example 2: a multi-level building
A FeatureCollection can't be nested, so the building's structure lives in properties. Each floor gets a footprint feature, and every room points to its floor with level_id.
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"id": "level-0",
"properties": { "category": "level", "level": 0, "level_name": "Ground floor", "building": "A" },
"geometry": { "type": "Polygon", "coordinates": [...] }
},
{
"type": "Feature",
"id": "level-1",
"properties": { "category": "level", "level": 1, "level_name": "First floor", "building": "A" },
"geometry": { "type": "Polygon", "coordinates": [...] }
},
{
"type": "Feature",
"id": "room-0-01",
"properties": { "category": "room", "name": "Reception", "level": 0, "level_id": "level-0" },
"geometry": { "type": "Polygon", "coordinates": [...] }
},
{
"type": "Feature",
"id": "room-1-02",
"properties": { "category": "room", "name": "Meeting room 2", "level": 1, "level_id": "level-1" },
"geometry": { "type": "Polygon", "coordinates": [...] }
}
]
}To show one floor, filter on level. Keeping both level (for filtering) and level_id (for joining) costs a few bytes and saves a lookup.
Example 3: doors, desks and points of interest
[
{
"type": "Feature",
"id": "door-1-07",
"properties": { "category": "door", "level": 1, "connects": ["room-1-02", "corridor-1"], "accessible": true },
"geometry": { "type": "Point", "coordinates": [-0.0903871, 51.5052214] }
},
{
"type": "Feature",
"id": "desk-1-14",
"properties": { "category": "desk", "level": 1, "room_id": "room-1-04", "bookable": true, "asset_tag": "D-114" },
"geometry": { "type": "Polygon", "coordinates": [...] }
}
]Example 4: styling and labels
Colours aren't part of GeoJSON. The simplestyle-spec keys are the de facto convention: geojson.io and GitHub read them, and FloorJSON both reads and writes them.
{
"type": "Feature",
"properties": {
"name": "Kitchen",
"stroke": "#ef4444",
"stroke-width": 2,
"fill": "#f0a020",
"fill-opacity": 0.5,
"label": "Kitchen",
"label_size": "l",
"label_color": "#ffffff"
},
"geometry": { "type": "Polygon", "coordinates": [...] }
}The keys FloorJSON exports
Every FloorJSON export uses the same keys, so code written against one file works on the next. Keys only appear when they apply to that feature.
| Key | Type | Meaning |
|---|---|---|
name | string | Room or shape name, from the SVG or typed in the editor |
group | string | null | Group the shape belongs to, typically a floor or zone |
shape_type | string | Where the shape came from: rect, path, polygon, circle, pen, point, radius, merged… |
source | string | "drawn" for shapes drawn in the editor; absent for shapes converted from the SVG |
fill, stroke, stroke-width, fill-opacity | string / number | simplestyle-spec colours and line width |
marker-color | string | Point and icon colour (simplestyle-spec) |
label, label_size, label_color | string | Text shown inside the shape; size is s, m, l or xl |
radius_m, center | number, [lng, lat] | For circles, which are exported as 64-point polygons |
icon_type, icon_name, icon_size | string, string, number | For animated icons: builtin, lottie or svg, and size in pixels |
centroid_lng, centroid_lat | number | Centre point of shapes converted from the SVG |
layer_rotation, layer_opacity | number | How the SVG was rotated and displayed when aligned |
| Your own attributes | string | Anything you add in the shape inspector, such as level or department |
The collection also carries a metadata object naming the generator and the CC BY 4.0 licence.
Example 5: a JSON Schema to validate against
A schema turns your conventions into something a build step can check. This one validates the room and door properties used above. It uses JSON Schema 2020-12 and works with validators such as Ajv.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/floorplan-feature.json",
"title": "Floor plan feature properties",
"type": "object",
"required": ["category", "level"],
"properties": {
"name": { "type": "string", "minLength": 1 },
"category": { "enum": ["level", "room", "corridor", "desk", "door", "stair", "poi"] },
"level": { "type": "integer", "minimum": -10, "maximum": 200 },
"level_name": { "type": "string" },
"level_id": { "type": "string" },
"room_id": { "type": "string" },
"area_m2": { "type": "number", "exclusiveMinimum": 0 },
"capacity_people": { "type": "integer", "minimum": 0 },
"connects": { "type": "array", "items": { "type": "string" }, "minItems": 2 },
"fill": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
"stroke": { "type": "string", "pattern": "^#[0-9a-fA-F]{6}$" },
"fill-opacity":{ "type": "number", "minimum": 0, "maximum": 1 }
},
"allOf": [
{ "if": { "properties": { "category": { "const": "door" } } },
"then": { "required": ["connects"] } }
]
}import Ajv from "ajv/dist/2020.js";
import fs from "node:fs";
const schema = JSON.parse(fs.readFileSync("floorplan-feature.schema.json", "utf8"));
const data = JSON.parse(fs.readFileSync("building.geojson", "utf8"));
const validate = new Ajv({ allErrors: true }).compile(schema);
for (const f of data.features) {
if (!validate(f.properties)) {
console.error(f.id ?? f.properties?.name, validate.errors);
}
}FAQ
Can I put any custom fields in GeoJSON properties?
Yes. The spec allows any JSON values in properties. The value of a schema is consistency: the same key, type and meaning in every file.Where should the feature ID go?
On the Feature itself as "id", which GeoJSON allows. Copy it into properties only if a tool you use can't read the top-level id.How should I structure a multi-level floor plan in JSON?
Keep one flat FeatureCollection. Add a level number to every feature and, if you need joins, a level_id that points to a level footprint feature.Does FloorJSON add a level property?
It exports a group per shape, which most people use for floors. If you need a numeric level, add it as a custom attribute in the shape inspector and it is exported with each feature.Generate this structure instead of typing it
Draw or upload your plan in FloorJSON and every feature is exported with the same keys: name, group, type, colours and labels, plus any custom attributes you add.
Open FloorJSON