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 handlingsetError— display validation errors in the formlayerName— user-editable layer namebeforeLayer— optional layer ID for insert orderingshell— access toaddLayer, 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/usinguseAddDataSourceandAddDataSourceForm - Implement validation and parsing logic that returns a GeoJSON FeatureCollection
- Build the layer object with
createBaseLayerand add it viasource.shell.addLayer() - Add i18n keys to
en.jsonfor all user-facing strings - Export the component—
AddDataMenu.tsxhandles 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →