Best Practices for Listing Files in GeoLibre: 5 Proven Methods for Desktop and Web

GeoLibre unifies five distinct file-listing strategies—Tauri native dialogs, HTML file inputs, drag-and-drop, remote catalogs, and plugin extensions—into a single addLayer pipeline that produces consistent GeoLibreLayer records in the central store.

Whether you're building a desktop geospatial application or deploying to the browser, understanding how GeoLibre lists and imports files is essential for extending its data pipeline. Each approach follows a dedicated code path but ultimately converges on the same store mutation in @geolibre/core, ensuring consistent layer handling regardless of source.

Understanding the Core Architecture

All file-listing methods in GeoLibre share a unified destination. When a user selects files through any supported mechanism, the resulting data flows through loaders like DuckDB-WASM (ST_Read) or shpjs, then enters the central store via addGeoJsonLayer or equivalent helpers.

This design means you can mix local files, remote URLs, and plugin-provided listings within the same project without duplicating import logic.

Method 1: Native File-System Dialogs (Tauri Desktop)

For desktop builds, GeoLibre leverages Tauri's native dialog API to provide system-native file browsing with full filesystem access.

The implementation lives in apps/geolibre-desktop/src/hooks/useAddData.ts, where window.__TAURI__.dialog.open returns absolute filesystem paths:

// apps/geolibre-desktop/src/hooks/useAddData.ts
export async function openFileDialog() {
  const files = await window.__TAURI__.dialog.open({
    multiple: true,
    filters: [{ name: 'Geo files', extensions: ['geojson', 'shp', 'zip', 'kml'] }],
  });
  if (!files) return;
  // `files` is an array of absolute paths
  for (const path of files) {
    await addGeoJsonLayerFromPath(path);
  }
}

Key characteristics:

  • Returns absolute paths that bypass browser sandboxing
  • Supports multi-select and extension filtering
  • Paths feed directly to DuckDB-WASM or specialized loaders

Unique layer IDs are generated via packages/map/src/vector-tile-layer-ids.ts before store insertion.

Method 2: HTML <input type="file"> (Web Build)

Browser deployments use standard HTML5 file inputs with identical filtering capabilities. The FileList object provides File instances read via FileReader or streamed to DuckDB-WASM.

From apps/geolibre-desktop/src/components/AddDataDialog.tsx:

// apps/geolibre-desktop/src/components/AddDataDialog.tsx
<input
  type="file"
  multiple
  accept=".geojson,.shp,.zip,.kml"
  onChange={e => {
    const list = e.target.files;
    if (list) {
      Array.from(list).forEach(file => addGeoJsonLayerFromFile(file));
    }
  }}
/>

The resulting GeoJSON passes through packages/map/src/style-mapper.ts to generate MapLibre-compatible sources and layers. Both desktop and web builds share the same downstream pipeline—only the acquisition mechanism differs.

Method 3: Drag-and-Drop Integration

GeoLibre treats drag-and-drop as a first-class input method. The DataTransfer object from drop events yields a FileList identical to file inputs, enabling code reuse.

Implementation in apps/geolibre-desktop/src/components/MapCanvas.tsx sets onDrop handlers that extract files and forward them to the standard import pipeline. For ZIP shapefiles, specialized handling in packages/processing/src/wasm-convert.ts decompresses and converts before layer creation.

This method is particularly valuable for rapid iteration—users can drag files directly onto the map canvas or sidebar without navigating dialogs.

Method 4: Remote File Browsing (Source Cooperative, Hugging Face)

Remote datasets require virtual file listings rather than filesystem access. GeoLibre's remote panel architecture fetches JSON catalogs and presents them as browseable interfaces.

The format support and constraints are defined in packages/plugins/src/plugins/remote-file-formats.ts:

// packages/plugins/src/plugins/remote-file-panels.tsx
{remoteCatalog.map(item => (
  <button key={item.id} onClick={() => addRemoteLayer(item.url, item.name)}>
    {item.name}
  </button>
))}

Selection triggers HTTP-streamed imports via DuckDB-WASM's ST_Read over HTTP, treating remote URLs similarly to local paths. This enables direct integration with Source Cooperative, Hugging Face datasets, and other cloud-hosted geospatial repositories.

Method 5: Plugin-Provided File Listings

GeoLibre's extensibility allows plugins to register custom file listers through a standardized API. Plugins expose arrays of file descriptors that the core UI consumes and renders.

Registration occurs via packages/plugins/src/plugins/plugin-api.ts:

// packages/plugins/src/plugins/plugin-api.ts
registerFileLister(sourceId: string, lister: FileLister): void;

The usePlugins hook in apps/geolibre-desktop/src/hooks/usePlugins.ts aggregates all registered listers and merges them into the Add Data dialog. This pattern enables cloud storage providers, database connectors, and proprietary formats to surface their contents without modifying core code.

The Unified Store Pipeline

Despite five distinct entry points, all paths converge on the same Zustand store mutation:

// Central store mutation (simplified)
import { useStore } from '@geolibre/core';

export function addGeoJsonLayer(data: GeoJSON.GeoJSON, name: string) {
  const id = generateLayerId();
  useStore.getState().addLayer({
    id,
    name,
    type: 'geojson',
    source: { type: 'geojson', data },
    // …additional UI metadata
  });
}

The store definition in packages/core/src/store.ts holds the layers array and provides addLayer. MapLibre synchronization happens automatically through @geolibre/map subscriptions.

Implementation Best Practices

Choose methods based on deployment target:

  • Desktop-only features → Prioritize Tauri dialogs for native UX
  • Cross-platform needs → Implement both Tauri and HTML input branches
  • Cloud-first workflows → Lead with remote panels and plugin extensions

Maintain loader compatibility:

Preserve layer ID uniqueness:

Summary

  • Five listing methods cover desktop, web, drag-and-drop, remote catalogs, and plugin extensions—all converging on unified store mutations
  • Tauri native dialogs provide absolute path access with system-native UX in useAddData.ts
  • HTML file inputs enable identical web deployment through FileReader or streaming
  • Drag-and-drop reuses file-input pipeline via DataTransfer in MapCanvas.tsx
  • Remote panels stream HTTP sources through DuckDB-WASM using format definitions in remote-file-formats.ts
  • Plugin listers extend the Add Data dialog without core modifications via registerFileLister

Frequently Asked Questions

What file formats does GeoLibre support for listing and import?

GeoLibre supports GeoJSON, Shapefile (including ZIP archives), KML, and additional vector formats through DuckDB-WASM's ST_Read. The specific extensions are defined in remote-file-formats.ts and dialog filter configurations. For web builds, format support depends on DuckDB-WASM capabilities and any additional WASM converters loaded.

Can I add custom remote data sources to the file listing?

Yes. Implement a plugin using the registerFileLister API in packages/plugins/src/plugins/plugin-api.ts. Your plugin returns file descriptors that the core UI renders automatically. This pattern powers the built-in Source Cooperative and Hugging Face integrations and supports any HTTP-accessible geospatial catalog.

How does GeoLibre handle large file listings without performance degradation?

Remote panels use virtualized lists for catalog browsing, and the store maintains layers by reference rather than deep copies. For actual data loading, DuckDB-WASM supports streaming reads over HTTP, avoiding full materialization until visualization requires it. The layer ID generation in vector-tile-layer-ids.ts remains constant-time regardless of catalog size.

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 →