How to Add a New Data Source to the Add Data Menu in GeoLibre

Create a source component in apps/geolibre-desktop/src/components/layout/add-data/sources/ that uses useAddDataSource and AddDataSourceForm, then export it to be automatically picked up by AddDataMenu.tsx.

The Add Data menu in GeoLibre is a modular system built from independent source components in the opengeos/GeoLibre repository. Each data source you see in the menu is a self-contained React component that handles its own UI, validation, and layer creation. This architecture makes it straightforward to extend GeoLibre with custom data loaders for proprietary formats, external APIs, or specialized file types.

This guide walks through the complete process of adding a new data source to the Add Data menu, from component implementation to registration and testing.

Create a New Source Component

Source components live in apps/geolibre-desktop/src/components/layout/add-data/sources/. Each follows a consistent pattern using shared utilities from the add-data/ module.

File Structure and Required Imports

Create a new .tsx file in the sources/ directory. All source components import from three main areas:

// apps/geolibre-desktop/src/components/layout/add-data/sources/MySource.tsx
import { useAppStore } from "@geolibre/core";           // Global state
import { Button, Label } from "@geolibre/ui";           // UI primitives
import { useTranslation } from "react-i18next";         // Localization
import { createBaseLayer, errorMessage } from "../helpers";
import { AddDataSourceForm, useAddDataSource } from "../shared";

The useAddDataSource Hook

The useAddDataSource hook in add-data/shared.tsx provides the boilerplate for source state management:

const source = useAddDataSource(t("addData.mySource.defaultName"));

This returns an object with:

  • runSubmit — wraps your submit logic with loading and error handling
  • setError — display validation errors in the form
  • layerName — user-editable layer name
  • beforeLayer — optional layer ID for insert ordering
  • shell — access to addLayer, map controller, and dialog controls

Implementing the Submit Handler

The core of your source is the runSubmit callback, which validates input, parses data, and constructs a GeoLibre layer:

const handleSubmit = source.runSubmit(async () => {
  // 1. Validate input
  if (!validInput) throw new Error(t("addData.mySource.errorInvalid"));
  
  // 2. Parse to GeoJSON FeatureCollection
  const featureCollection = await myParser(input);
  
  // 3. Build layer with createBaseLayer
  const layer = createBaseLayer(
    source.layerName.trim() || t("addData.mySource.defaultName"),
    "geojson",                              // layer type
    { type: "geojson" },                    // source config
    { sourceKind: "my-source" },            // metadata
    { geojson: featureCollection }          // data payload
  );
  
  // 4. Add to map and optionally fit view
  source.shell.addLayer(layer, source.beforeLayer);
  source.shell.mapControllerRef.current?.fitLayer(layer);
});

Complete Component Example

Here's a minimal implementation for a CSV vector source:

// apps/geolibre-desktop/src/components/layout/add-data/sources/CsvSource.tsx
import { useState } from "react";
import { useAppStore } from "@geolibre/core";
import { Button } from "@geolibre/ui";
import { useTranslation } from "react-i18next";
import { parseCsvToGeojson } from "../../../../lib/csv-parser";
import { createBaseLayer } from "../helpers";
import { AddDataSourceForm, useAddDataSource } from "../shared";

export function CsvSource() {
  const { t } = useTranslation();
  const source = useAddDataSource(t("addData.csv.defaultName"));
  const [file, setFile] = useState<File | null>(null);

  const handleChoose = async () => {
    source.setError(null);
    const files = await pickCsvFiles(); // platform-specific file picker
    if (files.length) setFile(files[0]);
  };

  const handleSubmit = source.runSubmit(async () => {
    if (!file) throw new Error(t("addData.csv.errorChooseFile"));
    const fc = await parseCsvToGeojson(file);
    const layer = {
      ...createBaseLayer(
        source.layerName.trim() || t("addData.csv.defaultName"),
        "geojson",
        { type: "geojson" },
        { sourceKind: "csv", featureCount: fc.features.length },
        { geojson: fc },
      ),
      geojson: fc,
    };
    source.shell.addLayer(layer, source.beforeLayer);
    source.shell.mapControllerRef.current?.fitLayer(layer);
  });

  return (
    <AddDataSourceForm
      source={source}
      onChoose={handleChoose}
      onSubmit={handleSubmit}
      description={t("addData.csv.description")}
      chooseLabel={t("addData.csv.choose")}
      submitLabel={t("common.add")}
    />
  );
}

Add Internationalization Strings

GeoLibre uses react-i18next for localization. Add translation keys to apps/geolibre-desktop/src/i18n/locales/en.json:

{
  "addData": {
    "csv": {
      "defaultName": "CSV Data",
      "description": "Import points, lines, or polygons from a CSV file",
      "choose": "Select CSV file...",
      "errorChooseFile": "Please select a CSV file to import"
    },
    "mySource": {
      "defaultName": "My Data Source",
      "errorInvalid": "Invalid input provided"
    }
  }
}

The useTranslation() hook automatically picks up these keys at runtime.

Register the Component in AddDataMenu

The AddDataMenu.tsx component automatically imports all source components from the sources/ directory:

// apps/geolibre-desktop/src/components/layout/toolbar/AddDataMenu.tsx
import * as sources from "../add-data/sources";

By simply exporting your component from its file, it becomes available in the menu without additional registration. For conditional visibility (e.g., platform-specific sources), return null early based on environment checks—see GdbSource.tsx for this pattern.

Update the Service Library (Optional)

For data sources that connect to external web services (WMS, WFS, custom APIs), add service definitions to add-data/service-library.ts:

export const serviceLibrary = {
  // existing services...
  "my-service": {
    name: "My Custom Service",
    urlPattern: "https://api.example.com/{z}/{x}/{y}",
    requiresAuth: true,
  },
};

Expose saved connections through ServiceLibrarySection.tsx so users can reuse them in the Add Data dialog.

Test Your Implementation

Run the frontend test suite to ensure your source doesn't break existing functionality:

npm run test:frontend

Add a unit test that mounts your component and verifies layer creation:

// tests/CsvSource.test.tsx
import { render, screen, waitFor } from "@testing-library/react";
import { CsvSource } from "../src/components/layout/add-data/sources/CsvSource";

test("CsvSource adds layer to store on submit", async () => {
  render(<CsvSource />);
  // Simulate file selection and submit
  // Assert layer exists in store
});

Key Source Files Reference

File Purpose
AddDataMenu.tsx Menu renderer; auto-imports all source components
add-data/sources/PhotosSource.tsx Complete reference implementation
add-data/sources/GdbSource.tsx Conditional rendering example
add-data/shared.tsx useAddDataSource hook and AddDataSourceForm
add-data/helpers.ts createBaseLayer, errorMessage utilities
add-data/context.tsx Dialog context (store, map controller)
i18n/locales/en.json Translation strings

Summary

Adding a new data source to GeoLibre's Add Data menu requires five core steps:

  • Create a source component in add-data/sources/ using useAddDataSource and AddDataSourceForm
  • Implement validation and parsing logic that returns a GeoJSON FeatureCollection
  • Build the layer object with createBaseLayer and add it via source.shell.addLayer()
  • Add i18n keys to en.json for all user-facing strings
  • Export the component—AddDataMenu.tsx handles registration automatically

Service-based sources additionally require updates to service-library.ts and ServiceLibrarySection.tsx.

Frequently Asked Questions

Does GeoLibre require manual registration of new data sources?

No. The AddDataMenu.tsx component uses a namespace import from add-data/sources/, so any exported component in that directory appears automatically. No registry edits or import additions are needed.

What data format must a source return?

Sources must ultimately produce a GeoJSON FeatureCollection. The createBaseLayer helper in add-data/helpers.ts wraps this into a GeoLibre layer object with metadata, source configuration, and styling defaults.

Can a source component be hidden on certain platforms?

Yes. Conditionally return null based on environment detection. The GdbSource.tsx implementation demonstrates this pattern for Mac App Store compatibility restrictions.

How do I add support for a completely new file format?

Create a parser in src/lib/ that converts your format to GeoJSON, then reference it in your source component's submit handler. The CSV example above follows this pattern with parseCsvToGeojson().

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 →