# How Raster and COG Layers Added via Plugins Integrate with the Style Panel's Paint Controls

> Discover how raster and COG layers from plugins seamlessly integrate with GeoLibre's Style panel. Learn how metadata flags control paint editors and opacity for enhanced styling.

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

---

**Raster and COG layers added via plugins integrate with GeoLibre's Style panel through metadata flags—specifically `sourceKind` and `paintMode`—which determine whether MapLibre-GL paint editors appear, get suppressed entirely, or show only a bridged opacity control.**

GeoLibre's Style panel automatically adapts its paint controls based on how a raster or Cloud-Optimized GeoTIFF (COG) layer enters the map. Layers registered through plugins carry special metadata that the panel inspects at runtime, distinguishing between MapLibre-native raster sources and plugin-rendered WebGL layers. This architecture lets plugins opt out of standard paint controls when they handle pixel rendering themselves, while still offering selective UI integration through opacity bridges.

---

## The Source Metadata That Flags Plugin Raster Layers

When a plugin registers a raster or COG layer, it populates a `sourceKind` field that the Style panel uses for initial categorization. According to the source code in [`apps/geolibre-desktop/src/components/panels/StylePanel.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/StylePanel.tsx) (lines 1736-1748), recognized values include:

- `cog-url`
- `geotiff-url`
- `maplibre-gl-raster`
- `stac-search-cog`

These values trigger the `isDeckRasterLayer` flag, grouping the layer under "Deck raster layers" in the panel's internal logic.

```typescript
// From StylePanel.tsx – layer classification based on sourceKind
const isDeckRasterLayer = [
  'cog-url',
  'geotiff-url',
  'maplibre-gl-raster',
  'stac-search-cog'
].includes(layer.sourceKind);

```

This classification alone does not determine paint control visibility—it merely identifies the layer's origin as plugin-mediated.

---

## The `paintMode` Flag: Controlling Paint Editor Visibility

The critical decision point for paint control integration lives in the `paintMode` property. Defined in [`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts) (lines 61-66), this optional field accepts `"plugin"` as a value to indicate the plugin renders pixels independently.

The helper function `pluginOwnsPaint`, exported from `@geolibre/core` and implemented in [`packages/core/src/external-native-paint.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/external-native-paint.ts) (lines 10-16), performs the runtime check:

```typescript
// packages/core/src/external-native-paint.ts
export function pluginOwnsPaint(metadata: LayerMetadata): boolean {
  return metadata.paintMode === 'plugin';
}

```

When `pluginOwnsPaint` returns `true`, the Style panel suppresses all MapLibre-GL paint editors because the plugin's WebGL layer occupies no MapLibre paint slot. The source commentary in [`StylePanel.tsx`](https://github.com/opengeos/GeoLibre/blob/main/StylePanel.tsx) (lines 1752-1756) explicitly notes this behavior: no MapLibre paint properties exist to edit when the plugin owns rendering.

---

## Bridged Opacity: Selective UI Integration

Plugins that suppress standard paint controls can still expose limited styling through a **paint bridge**. If a plugin registers an `opacity` handler in its `paintBridge` configuration, the Style panel detects this via the `hasBridgedOpacity` flag (lines 1758-1760) and renders a single Opacity slider.

```typescript
// Example: COG layer registration with paintMode and opacity bridge
// packages/plugins/src/plugins/cog-layer.ts
import { registration } from "@geolibre/core";

export const cogLayer = registration({
  id: "my-cog-layer",
  source: {
    type: "cog-url",
    url: "https://example.com/data/my.tif",
  },
  paintMode: "plugin",           // Suppresses MapLibre paint editors
  paintBridge: {
    opacity: (value: number) => {
      raster.setOpacity(value);  // Enables Opacity slider only
    },
  },
});

```

This bridged approach satisfies common use cases—users expect opacity control for overlay rasters—without requiring full MapLibre paint property implementation.

---

## Conditions for Raster-Specific Controls

The full suite of raster paint editors—brightness, contrast, saturation, and hue-rotate—appears only when a layer satisfies the compound condition in [`StylePanel.tsx`](https://github.com/opengeos/GeoLibre/blob/main/StylePanel.tsx) (lines 1776-1779):

```typescript
!isPluginPaintedLayer && (
  isRasterPaintLayer(layer.type) ||
  isRasterTileLayer ||
  isDeckRasterLayer
)

```

This logic creates three distinct UI outcomes:

| Scenario | `paintMode` | `paintBridge.opacity` | Visible Controls |
|----------|-------------|----------------------|------------------|
| Plugin-rendered COG, no bridge | `"plugin"` | absent | None |
| Plugin-rendered COG with opacity bridge | `"plugin"` | present | Opacity slider only |
| Native raster source (no plugin) | undefined or omitted | N/A | Full raster paint editors (opacity, brightness, contrast, saturation, hue-rotate) |

---

## Implementation Files and Verification

The integration behavior is enforced across several key files:

- **[`apps/geolibre-desktop/src/components/panels/StylePanel.tsx`](https://github.com/opengeos/GeoLibre/blob/main/apps/geolibre-desktop/src/components/panels/StylePanel.tsx)** — Main UI logic for paint control rendering; contains `isDeckRasterLayer`, `isPluginPaintedLayer`, and `hasBridgedOpacity` evaluations.

- **[`packages/core/src/external-native-paint.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/core/src/external-native-paint.ts)** — Exports `pluginOwnsPaint` for checking `metadata.paintMode`.

- **[`packages/plugins/src/types.ts`](https://github.com/opengeos/GeoLibre/blob/main/packages/plugins/src/types.ts)** — Type definitions including the optional `paintMode` property for plugin registrations.

- **[`tests/plugin-owned-paint.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/plugin-owned-paint.test.ts)** — Test suite confirming `paintMode` is correctly stored and interpreted in layer metadata.

---

## Summary

- **Plugin raster layers** are recognized by `sourceKind` values like `cog-url` and flagged as deck raster layers.
- **The `paintMode: "plugin"` flag** suppresses all MapLibre-GL paint editors when plugins render pixels independently.
- **Opacity bridges** (`paintBridge.opacity`) restore limited UI control, showing only the Opacity slider.
- **Full raster controls** require native MapLibre sources or plugins that omit `paintMode: "plugin"`.
- The `pluginOwnsPaint` helper in `@geolibre/core` centralizes the runtime ownership check.

---

## Frequently Asked Questions

### What happens if a plugin doesn't set `paintMode`?

The Style panel treats the layer as MapLibre-native, rendering the full set of raster paint editors. This may cause runtime errors if the plugin actually handles its own WebGL rendering, since no valid MapLibre paint properties exist for the layer type.

### Can plugins expose controls beyond opacity through paint bridges?

The current architecture in GeoLibre supports only opacity bridging for plugin-painted layers. Additional paint properties would require extending the `paintBridge` interface or implementing custom panel components registered by the plugin itself.

### How do I test whether my plugin's paint mode is correctly detected?

Import and invoke `pluginOwnsPaint` from `@geolibre/core` in your test suite, passing the layer metadata object. The test file [`tests/plugin-owned-paint.test.ts`](https://github.com/opengeos/GeoLibre/blob/main/tests/plugin-owned-paint.test.ts) demonstrates the expected behavior: `paintMode: "plugin"` returns `true`, while absent or other values return `false`.