# Validate connections

Use declarative port types for ordinary data compatibility, then add a custom validator for rules that depend on the nodes or ports themselves. The rendered diagram offers matching targets, refuses vetoed targets before a link is created, and keeps the reason returned by your validator.

## Choose the rule

See [Ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules) for `dataType`, `PortSpec`, `portTypeRegistry`, `compatibleWith`, and `registerConnectionValidator`. This page adds complete typed-port examples and a validator whose returned reason explains why a proposed target is refused.

Validators are process-global. Keep the disposer returned by `registerConnectionValidator()` and call it when the mounted diagram leaves the page; do not clear validators owned by another diagram.

## Build typed ports

The following data gives the diagram one number output, one number input, and one string input. Registering `number` as compatible only with `number` makes the number-to-number drop valid and the number-to-string drop invalid. The number glyphs use the registered blue and the string glyph uses the registered purple.

### JavaScript

Use [`render`](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/grafloria-element-core) to mount the spec into a real element.

```html
<div id="diagram" style="height: 520px"></div>
<script type="module">
  import { render } from '@grafloria/element';
  import { portTypeRegistry } from '@grafloria/engine';

  portTypeRegistry.registerAll([
    { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
    { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
  ]);

  const nodes = [
    { id: 'source', position: { x: 80, y: 190 }, size: { width: 140, height: 70 }, label: 'number source', ports: [{ id: 'out', side: 'right', type: 'output', dataType: 'number' }] },
    { id: 'number', position: { x: 460, y: 100 }, size: { width: 140, height: 70 }, label: 'number input', ports: [{ id: 'in', side: 'left', type: 'input', dataType: 'number' }] },
    { id: 'string', position: { x: 460, y: 280 }, size: { width: 140, height: 70 }, label: 'string input', ports: [{ id: 'in', side: 'left', type: 'input', dataType: 'string' }] },
  ];

  const container = document.getElementById('diagram');
  if (!(container instanceof HTMLElement)) throw new Error('Missing diagram container');
  const instance = render({ nodes, edges: [] }, container);
  instance.renderNow();
</script>
```

### React

```tsx
import { GrafloriaFlow } from '@grafloria/react';
import type { NodeInput } from '@grafloria/renderer';
import { portTypeRegistry } from '@grafloria/engine';

portTypeRegistry.registerAll([
  { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
  { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
]);

const nodes = [
  { id: 'source', position: { x: 80, y: 190 }, size: { width: 140, height: 70 }, label: 'number source', ports: [{ id: 'out', side: 'right' as const, type: 'output' as const, dataType: 'number' }] },
  { id: 'number', position: { x: 460, y: 100 }, size: { width: 140, height: 70 }, label: 'number input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'number' }] },
  { id: 'string', position: { x: 460, y: 280 }, size: { width: 140, height: 70 }, label: 'string input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'string' }] },
] satisfies NodeInput[];

export function TypedDiagram() {
  return <div style={{ height: 520 }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={[]} /></div>;
}
```

### Vue

```vue
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
import { portTypeRegistry } from '@grafloria/engine';
import type { NodeInput } from '@grafloria/renderer';

portTypeRegistry.registerAll([
  { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
  { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
]);

const nodes = [
  { id: 'source', position: { x: 80, y: 190 }, size: { width: 140, height: 70 }, label: 'number source', ports: [{ id: 'out', side: 'right' as const, type: 'output' as const, dataType: 'number' }] },
  { id: 'number', position: { x: 460, y: 100 }, size: { width: 140, height: 70 }, label: 'number input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'number' }] },
  { id: 'string', position: { x: 460, y: 280 }, size: { width: 140, height: 70 }, label: 'string input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'string' }] },
] satisfies NodeInput[];
</script>

<template><div style="height:520px"><GrafloriaFlow :default-nodes="nodes" :default-edges="[]" /></div></template>
```

### Angular

```ts
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import { portTypeRegistry } from '@grafloria/engine';
import type { NodeInput } from '@grafloria/renderer';

portTypeRegistry.registerAll([
  { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
  { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
]);

@Component({
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `<grafloria-diagram-canvas [(nodes)]="nodes" [(edges)]="edges" style="display:block;height:520px" />`,
})
export class TypedDiagramComponent {
  nodes = [
    { id: 'source', position: { x: 80, y: 190 }, size: { width: 140, height: 70 }, label: 'number source', ports: [{ id: 'out', side: 'right' as const, type: 'output' as const, dataType: 'number' }] },
    { id: 'number', position: { x: 460, y: 100 }, size: { width: 140, height: 70 }, label: 'number input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'number' }] },
    { id: 'string', position: { x: 460, y: 280 }, size: { width: 140, height: 70 }, label: 'string input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const, dataType: 'string' }] },
  ] satisfies NodeInput[];
  edges = [];
}
```

### Qwik

In a Qwik component, import `GrafloriaFlow` from `@grafloria/qwik`. Pass the typed `nodes` and an empty `defaultEdges` list, register the data types in the browser lifecycle, and call `renderNow()` from `onInit$` after the instance is available. Give the wrapper a height so the diagram has a drawable host.

## Add a custom validator

This rule allows output-to-input connections and rejects output-to-output connections. The callback receives a connection candidate, so the same rule works for a newly drawn link and for a link being reconnected. Return the reason string to expose why the proposed target is refused.

```tsx
import { useEffect } from 'react';
import { GrafloriaFlow } from '@grafloria/react';
import { registerConnectionValidator } from '@grafloria/renderer';
import type { NodeInput } from '@grafloria/renderer';

const nodes = [
  { id: 'source', position: { x: 80, y: 190 }, size: { width: 140, height: 70 }, label: 'source', ports: [{ id: 'out', side: 'right' as const, type: 'output' as const }] },
  { id: 'input', position: { x: 460, y: 100 }, size: { width: 140, height: 70 }, label: 'input', ports: [{ id: 'in', side: 'left' as const, type: 'input' as const }] },
  { id: 'other-output', position: { x: 460, y: 280 }, size: { width: 140, height: 70 }, label: 'other output', ports: [{ id: 'out', side: 'left' as const, type: 'output' as const }] },
] satisfies NodeInput[];

export function ValidatedDiagram() {
  useEffect(() => {
    const disposeValidator = registerConnectionValidator(({ sourcePort, targetPort }) => {
      if (sourcePort === null || targetPort === null) return true;
      if (sourcePort.type === 'output' && targetPort.type === 'output') {
        return 'An output cannot feed another output';
      }
      return true;
    });
    return disposeValidator;
  }, []);

  return <div style={{ height: 520 }}><GrafloriaFlow defaultNodes={nodes} defaultEdges={[]} /></div>;
}
```

Mount that registration with the diagram rather than at module load. In React, return `disposeValidator` from the effect cleanup. In Vue, call it from `onBeforeUnmount`. In Angular, call it from `ngOnDestroy`. In JavaScript, retain the disposer until you remove the diagram. The live [connection-validation demo](https://grafloria.com/demos/ports/connection-validation.html) shows an output-to-input wire being accepted and an output-to-output wire being refused.

## Options that affect connection legality

| Option | Type | Default | What it does |
|---|---|---:|---|
| `dataType` | `string` | — | Gives a port a registered data-flow type. The type drives glyph colour and compatibility. |
| `gating.allowedTypes` | `string[]` | unrestricted | Restricts which port data types may attach. |
| `gating.isConnectableStart` | `boolean` | `true` | Controls whether a link may start at the port. |
| `gating.isConnectableEnd` | `boolean` | `true` | Controls whether a link may end at the port. |
| `gating.fromMaxLinks` | `number \| null` | unlimited | Caps outgoing links. |
| `gating.toMaxLinks` | `number \| null` | unlimited | Caps incoming links. |
| `gating.allowSelfLink` | `boolean` | `false` | Controls whether a node may connect to itself. |
| `gating.allowDuplicateLinks` | `boolean` | `true` | Controls whether a second link between the same ordered ports is allowed. |

## Pitfalls

- A type registry is process-wide. Register application types during bootstrap and choose names that do not collide with another diagram.
- A custom validator is also process-global, and every registered validator must pass. Dispose your registration when its owner unmounts; see [ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules) for the registry boundary.
- A validator does not replace declarative port gating. Use `PortSpec` and its `gating` fields for direction and link caps, then reserve the validator for rules that need candidate context.

## Related

- [Ports and connection rules](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/ports-and-connection-rules) — port anatomy, gating, and compatibility.
- [How Grafloria works](https://bench-grafloria-56.atloria.app/p/bench-grafloria-56-62cg8vR5rF/developer/how-grafloria-works) — the model and engine behind every binding.
