Developing with the GeoLibre File System: A Complete Guide to `.geolibre.json`

GeoLibre stores every workspace as a single .geolibre.json file, providing a complete, portable representation of maps, layers, styles, and dashboard widgets.

This guide covers how to work with GeoLibre's file system when building applications with the opengeos/GeoLibre repository. Whether you're creating UI features, automating tests, or extending the platform, understanding the .geolibre.json format and its associated APIs is essential.

What Is the .geolibre.json File Format?

Every GeoLibre project persists to a single JSON file with the .geolibre.json extension. This file contains the entire application state: map view configuration, layer definitions, style libraries, plugin settings, story-map chapters, and dashboard widgets.

The format is documented in [docs/project-format.md](docs/project-format.md) and follows a strict schema with version tracking, enabling forward compatibility as the platform evolves.

Core Schema Structure

A minimal valid .geolibre.json includes:

  • version: Schema version (e.g., "0.1.0")
  • name: Human-readable project name
  • mapView: Center, zoom, bearing, and pitch
  • basemapStyleUrl, basemapVisible, basemapOpacity: Basemap configuration
  • layers: Array of layer objects with source and style references
  • styles: Style library definitions

File Name Handling and Sanitization

GeoLibre enforces consistent file naming through pure utility functions in [apps/geolibre-desktop/src/lib/file-names.ts](apps/geolibre-desktop/src/lib/file-names.ts).

ensureProjectFileName()

The ensureProjectFileName function guarantees valid project file names by:

  • Trimming whitespace from user input
  • Appending .geolibre.json if the extension is missing
  • Preserving existing extensions when already present
import { ensureProjectFileName } from "@/lib/file-names";

const rawName = " MyMap  ";
const safeName = ensureProjectFileName(rawName);
// safeName === "MyMap.geolibre.json"

When users provide blank names, the system falls back to DEFAULT_PROJECT_NAME as defined in the same module.

Loading Projects into the Store

Project loading is centralized in the Zustand store at [packages/core/src/store.ts](packages/core/src/store.ts). The loadProject method handles the complete state replacement.

loadProject() Signature and Behavior

loadProject(project: Project, path?: string, options?: LoadOptions): void

Located at useAppStore.loadProject, this method:

  1. Clears transient UI state (editor overlays, selected layers)
  2. Populates the store with parsed project data
  3. Updates the recent-projects list (unless rememberRecent: false)
  4. Resolves XYZ-based layer URLs when loading from external sources

The function accepts projects from multiple origins: UI file dialogs, URL hash parameters, drag-and-drop events, or the embed bridge API.

Saving Projects: Desktop and Web

GeoLibre abstracts platform differences through [apps/geolibre-desktop/src/lib/tauri-io.ts](apps/geolibre-desktop/src/lib/tauri-io.ts).

Save Methods

Method Platform Use Case
saveProjectFile(content) Desktop/Web Interactive save with native dialog or browser download
saveProjectFileToPath(content, path) Desktop only Programmatic writes to specific paths

The implementation at tauri-io.ts#L2947-L2993 handles:

  • Desktop: Tauri file dialog with permission handling
  • Web: Automatic download trigger with suggested filename
import { saveProjectFileToPath } from "@/lib/tauri-io";
import { useAppStore } from "@geolibre/core";

async function saveCurrentProject() {
  const json = JSON.stringify(useAppStore.getState().project);
  const path = "/tmp/my-project.geolibre.json";
  await saveProjectFileToPath(json, path);
}

Source: saveProjectFileToPath

Project Lifecycle Hooks

The [useProjectFileActions.ts](apps/geolibre-desktop/src/hooks/useProjectFileActions.ts) hook orchestrates the complete project lifecycle:

  • New project: Generates empty JSON with UUID via newProject()
  • Load project: Parses JSON, resolves layer URLs, injects into store
  • Save project: Serializes current store state to JSON

These actions combine file-name sanitization, I/O operations, and state management into cohesive UI workflows.

Loading from URLs and External Sources

The [useProjectUrlLoader.ts](apps/geolibre-desktop/src/hooks/useProjectUrlLoader.ts#L12-L37) hook enables project loading from remote URLs:

import { useProjectUrlLoader } from "@/hooks/useProjectUrlLoader";

function openFromUrl(url: string) {
  // Fetches file, resolves XYZ layers, calls loadProject internally
  useProjectUrlLoader({ projectUrl: url });
}

Security note: URL validation is enforced through an allow-list in [embed-api.ts](apps/geolibre-desktop/src/lib/embed-api.ts). Only approved domains may load projects via the embed bridge.

Creating Projects Manually for Testing

For development and automated testing, you can construct minimal valid projects:

{
  "version": "0.1.0",
  "name": "Blank Map",
  "mapView": { "center": [0, 0], "zoom": 2, "bearing": 0, "pitch": 0 },
  "basemapStyleUrl": "",
  "basemapVisible": true,
  "basemapOpacity": 1,
  "layers": [],
  "styles": {}
}

Save as blank.geolibre.json and open via UI or loadProject() to verify round-trip serialization.

Testing the File System

Unit and integration tests demonstrate proper usage:

These tests verify that project state survives serialization without data loss.

Summary

  • Single-file architecture: GeoLibre uses .geolibre.json as the authoritative project representation
  • Platform abstraction: saveProjectFile() and saveProjectFileToPath() handle desktop and web I/O uniformly
  • Centralized loading: loadProject() in packages/core/src/store.ts manages all state replacement
  • Validation layers: ensureProjectFileName() and embed API allow-lists enforce security and consistency
  • Testable design: Pure utilities enable unit testing; integration tests cover full workflows

Frequently Asked Questions

What happens if I rename a .geolibre.json file manually?

The file will load correctly as long as the extension remains .geolibre.json. The name field inside the JSON controls the display name, not the filesystem name. Use ensureProjectFileName() in your code to maintain consistency.

Can I version control GeoLibre projects?

Yes. The .geolibre.json format is plain JSON with deterministic key ordering, making it suitable for Git. The version field enables migration logic when opening files created with older schema versions.

How do I load a project from a URL without adding it to recent projects?

Pass rememberRecent: false in the options parameter: loadProject(project, path, { rememberRecent: false }). This is used by useProjectUrlLoader for embed scenarios where the source URL should not persist in the user's project history.

Is the file format stable across GeoLibre versions?

The schema is versioned via the version field. According to the source in [docs/project-format.md](docs/project-format.md), backward compatibility is maintained where possible, with automatic migration for older formats. Always specify the version when creating files programmatically.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →