# GeoLibre API Best Practices: A Complete Guide to Modular, Store‑Driven GIS Development

> Master GeoLibre API best practices for modular, store-driven GIS development. Use Zustand store, optional Python calls, and plugin registration for robust applications.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: best-practices
- Published: 2026-08-16

---

**Use the typed Zustand store as your single source of truth, keep Python sidecar calls optional with graceful fallbacks, and register plugins before UI mount to build robust GeoLibre applications.**

The GeoLibre API follows a **store‑driven, modular architecture** that cleanly separates client‑side JavaScript state management from optional Python backend services. This design enables both lightweight browser‑only deployments and full‑featured desktop applications with heavy‑weight geoprocessing capabilities. Understanding these architectural layers is essential for writing maintainable, performant code according to the `opengeos/GeoLibre` source.

---

## Core Architecture Overview

GeoLibre organizes functionality into distinct layers, each with clear responsibilities and entry points. Working with—rather than around—these layers ensures state consistency and predictable behavior.

| Layer | Purpose | Primary Entry Point | Source Location |
|-------|---------|---------------------|-----------------|
| **Core store** | Holds project state ([`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json) schema) via Zustand | `useStore` | [[`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) |
| **Map layer sync** | Reconciles store layers with MapLibre GL JS | `MapController.syncLayers` | [[`packages/map/src/MapController.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapController.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapController.ts) |
| **Plugin system** | Registers built‑in and external plugins | `usePlugins` hook | [[`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts) |
| **Processing engine** | Client‑side vector/raster operations | `process.runTool` | [[`packages/processing/src/processing.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/processing.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/processing.ts) |
| **Python sidecar** | Optional FastAPI backend for heavy tasks | `/vector/*`, `/raster/*`, `/conversion/*` | [[`backend/geolibre_server/app/main.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py)](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py) |
| **Embedding API** | Anywidget and iframe embedding | `geolibre-embed` package | [[`packages/embed/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts) |

---

## Best Practice 1: Always Work Through the Store

Direct manipulation of MapLibre GL JS instances bypasses GeoLibre's state management and causes UI desynchronization. The `useStore` Zustand store in [[`packages/core/src/store.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/store.ts) serves as the single source of truth.

Store mutations automatically trigger synchronization via [`MapController.syncLayers`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapController.ts). Use the provided helpers rather than raw map operations.

```typescript
import { useStore } from '@geolibre/core';
import { addGeoJsonLayer } from '@geolibre/map';

async function loadGeoJson(url: string) {
  const response = await fetch(url);
  const data = await response.json();
  const store = useStore.getState();
  
  // Dispatch store action — MapController syncs automatically
  addGeoJsonLayer(store, {
    id: `geojson-${Date.now()}`,
    data,
    sourceLayer: '',
    paint: { 'fill-color': '#3388ff', 'fill-opacity': 0.5 },
  });
}

```

---

## Best Practice 2: Leverage TypeScript Definitions for API Safety

GeoLibre exports comprehensive types from [[`packages/core/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/types.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/types.ts). Import these rather than constructing raw objects to catch errors at compile time.

Key type categories include:
- **`LayerConfig`** — Complete layer configuration with source, paint, and layout properties
- **`ProjectSchema`** — Root [`.geolibre.json`](https://github.com/opengeos/GeoLibre/blob/main/.geolibre.json) structure
- **`ProcessingTool`** — Interface for custom processing tools

---

## Best Practice 3: Keep Python Sidecar Usage Optional

The FastAPI sidecar endpoints in [[`backend/geolibre_server/app/main.py`](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py)](https://github.com/opengeos/GeoLibre/blob/main/backend/geolibre_server/app/main.py) provide **vector** (`/vector/*`), **raster** (`/raster/*`), and **conversion** (`/conversion/*`) operations. However, not all deployments include this backend.

**Always detect sidecar availability** before calling `/sidecar` endpoints. The client falls back to the browser‑based engine (Turf.js, DuckDB‑WASM) automatically. Calling the HTTP API when the sidecar isn't running throws a **502 error**.

```typescript
async function convertRaster(inputPath: string, outputPath: string) {
  // Check sidecar health first
  const health = await fetch('/sidecar/health').catch(() => null);
  if (!health?.ok) {
    throw new Error('Python sidecar unavailable; use client-side processing instead');
  }
  
  const resp = await fetch('/sidecar/conversion/raster', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ input: inputPath, output: outputPath }),
  });
  
  if (!resp.ok) {
    const err = await resp.json();
    throw new Error(`Conversion failed: ${err.detail}`);
  }
  return await resp.json(); // { status: 'ok', output: ... }
}

```

---

## Best Practice 4: Handle Async Errors Uniformly

All GeoLibre API functions return **Promises that reject with `GeoLibreError`**. Wrap calls in `try...catch` blocks and surface the `message` property to users. Never swallow errors silently.

```typescript
try {
  await process.runTool('buffer', { distance: 1000 });
} catch (error) {
  if (error instanceof GeoLibreError) {
    showNotification(error.message); // User-facing error
    console.error(error.code, error.stack); // Debugging info
  }
}

```

---

## Best Practice 5: Register Plugins Before UI Mount

Plugins load in [[`apps/geolibre-desktop/src/hooks/usePlugins.ts`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/hooks/usePlugins.ts). Custom plugins must call `registerPlugin` before React renders the main UI to ensure proper initialization order.

```typescript
import { registerPlugin } from '@geolibre/plugins';
import MyPlugin from './my-plugin';

// Register before app mount
registerPlugin({
  id: 'my-plugin',
  name: 'My Awesome Plugin',
  component: MyPlugin,
  description: 'Provides a custom data source',
});

```

Plugin registration after mount may cause undefined behavior in layer synchronization.

---

## Best Practice 6: Respect Remote File Size Limits

The remote file formats plugin enforces a **2 GiB limit** defined as `MAX_REMOTE_FILE_BYTES` in [[`packages/plugins/src/plugins/remote-file-formats.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/remote-file-formats.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/plugins/remote-file-formats.ts). Validate file sizes client‑side to avoid opaque failures.

```typescript
const MAX_REMOTE_FILE_BYTES = 2 * 1024 * 1024 * 1024; // 2 GiB

async function loadRemoteFile(url: string) {
  const head = await fetch(url, { method: 'HEAD' });
  const size = parseInt(head.headers.get('content-length') || '0');
  
  if (size > MAX_REMOTE_FILE_BYTES) {
    throw new Error(`File exceeds 2 GiB limit: ${size} bytes`);
  }
  // Proceed with loading...
}

```

---

## Best Practice 7: Use i18n for All User-Facing Strings

GeoLibre uses `react-i18next` for internationalization. Wrap all UI text with the `t()` function and add keys to [[`apps/geolibre-desktop/src/i18n/locales/en.json`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/en.json)](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/en.json) as the source of truth for translations.

```typescript
import { useTranslation } from 'react-i18next';

function LayerPanel() {
  const { t } = useTranslation();
  
  return (
    <button onClick={addLayer}>
      {t('layers.addButton')} // "Add Layer" or localized equivalent
    </button>
  );
}

```

---

## Best Practice 8: Control Embedding via Query Parameters

The embedding API in [[`packages/embed/src/index.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/embed/src/index.ts) supports reproducible configuration through URL parameters:

| Parameter | Purpose | Example |
|-----------|---------|---------|
| `locale` | Language code | `?locale=de` |
| `theme` | UI theme | `?theme=dark` |
| `center` | Initial map center | `?center=40.7128,-74.0060` |
| `zoom` | Initial zoom level | `?zoom=12` |

Use these for documentation, reports, and automated testing scenarios.

---

## Summary

- **Work through the store** — Use `useStore` and helper functions; never manipulate MapLibre directly
- **Type everything** — Import definitions from `@geolibre/core` for compile‑time safety
- **Make sidecar optional** — Detect availability; fall back to client‑side processing
- **Handle errors explicitly** — Catch `GeoLibreError` and surface meaningful messages
- **Register plugins early** — Call `registerPlugin` before React mount
- **Validate file sizes** — Respect the 2 GiB `MAX_REMOTE_FILE_BYTES` limit
- **Internationalize strings** — Use `t()` with keys from the locales file
- **Configure embedding** — Use query parameters for reproducible deployments

---

## Frequently Asked Questions

### How do I check if the Python sidecar is available before making API calls?

Make a lightweight request to `/sidecar/health` or catch connection errors on your first API call. The desktop app spawns the sidecar on demand, so availability depends on installation status. When unavailable, use the client‑side processing engine in [[`packages/processing/src/processing.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/processing.ts)](https://github.com/opengeos/GeoLibre/blob/main/packages/processing/src/processing.ts).

### Can I use GeoLibre without the Python backend entirely?

Yes. The core JavaScript API provides full functionality for browser‑based GIS workflows. The Python sidecar adds heavy‑weight operations (Whitebox GIS, rasterio, GeoPandas) as an optional enhancement. Design your code to gracefully degrade when the sidecar is absent.

### Where should I define custom processing tools?

Extend the processing engine by implementing the `ProcessingTool` interface and registering via `process.registerTool`. Place tool definitions in your plugin or in a dedicated package, then import and register them in your application's initialization code before the first tool invocation.

### How do I debug layer synchronization issues?

Enable Zustand devtools to inspect store state changes. Verify that your code uses store actions (`addGeoJsonLayer`, `updateLayer`, `removeLayer`) rather than direct MapLibre API calls. Check [`MapController.syncLayers`](https://github.com/opengeos/GeoLibre/blob/main/packages/map/src/MapController.ts) for the reconciliation logic that applies store changes to the map.