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

> Learn to add a new data source to the Add Data menu in GeoLibre. Create a component using useAddDataSource and export it for automatic integration.

- Repository: [Open Geospatial Solutions/GeoLibre](https://github.com/opengeos/GeoLibre)
- Tags: how-to-guide
- Published: 2026-08-01

---

**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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```tsx
// 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`](https://github.com/opengeos/GeoLibre/blob/main/add-data/shared.tsx) provides the boilerplate for source state management:

```tsx
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:

```tsx
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**:

```tsx
// 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`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/i18n/locales/en.json):

```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:

```tsx
// 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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/add-data/service-library.ts)**:

```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`](https://github.com/opengeos/GeoLibre/blob/main/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:

```bash
npm run test:frontend

```

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

```tsx
// 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`](https://github.com/opengeos/GeoLibre/blob/main/AddDataMenu.tsx) | Menu renderer; auto-imports all source components |
| [`add-data/sources/PhotosSource.tsx`](https://github.com/opengeos/GeoLibre/blob/main/add-data/sources/PhotosSource.tsx) | Complete reference implementation |
| [`add-data/sources/GdbSource.tsx`](https://github.com/opengeos/GeoLibre/blob/main/add-data/sources/GdbSource.tsx) | Conditional rendering example |
| [`add-data/shared.tsx`](https://github.com/opengeos/GeoLibre/blob/main/add-data/shared.tsx) | `useAddDataSource` hook and `AddDataSourceForm` |
| [`add-data/helpers.ts`](https://github.com/opengeos/GeoLibre/blob/main/add-data/helpers.ts) | `createBaseLayer`, `errorMessage` utilities |
| [`add-data/context.tsx`](https://github.com/opengeos/GeoLibre/blob/main/add-data/context.tsx) | Dialog context (store, map controller) |
| [`i18n/locales/en.json`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/en.json) for all user-facing strings
- **Export** the component—[`AddDataMenu.tsx`](https://github.com/opengeos/GeoLibre/blob/main/AddDataMenu.tsx) handles registration automatically

Service-based sources additionally require updates to [`service-library.ts`](https://github.com/opengeos/GeoLibre/blob/main/service-library.ts) and [`ServiceLibrarySection.tsx`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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`](https://github.com/opengeos/GeoLibre/blob/main/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()`.