# Configure ports

Declare connection points on each node to control their glyphs, labels, shared layout, and data-type rules; the same node specs work across Grafloria's framework bindings.

## When to use declared ports

Use declared ports when a node needs named inputs and outputs, more than the four default connection points, or visible distinctions between connection types. The graph data belongs to the diagram model; the framework binding converts your node specs into live models, and the instance connects the rendered canvas to engine behavior.

Each node can declare a `ports` array on its [`NodeSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-nodespec#nodespec). Give each port an id, then add a `shape`, a `label`, a `group`, or a `dataType` as needed. Nodes without `ports` keep their four deterministic default ports.

## Declare and mount the ports

The samples below all render the same small data-flow diagram: a `Source` node has number and string outputs, and a `Convert` node has matching inputs. Both nodes arrange their ports in named side groups. The port glyphs have different shapes and labels; the initial number-to-number link is present in every sample. A registered type palette adds type colours and can declare compatibility beyond exact type-name matches.

The JavaScript and framework mounting patterns are covered in [Build runnable workflows](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/build-runnable-workflows); this page adds port declarations and a registered type palette to the node data.

:::code-group
```ts title="JavaScript"
import {
  render,
  portTypeRegistry,
  type EdgeSpec,
  type NodeSpec,
} from '@grafloria/element';

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

const nodes: NodeSpec[] = [
  {
    id: 'source',
    position: { x: 80, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Source',
    metadata: {
      portGroups: {
        outputs: {
          id: 'outputs',
          side: 'right',
          visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-out',
        group: 'outputs',
        type: 'output',
        dataType: 'number',
        shape: { shape: 'circle', size: 14 },
        label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-out',
        group: 'outputs',
        type: 'output',
        dataType: 'string',
        shape: { shape: 'square', size: 14 },
        label: { text: 'string', layout: 'outside' },
      },
    ],
  },
  {
    id: 'convert',
    position: { x: 430, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Convert',
    metadata: {
      portGroups: {
        inputs: {
          id: 'inputs',
          side: 'left',
          visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-in',
        group: 'inputs',
        type: 'input',
        dataType: 'number',
        shape: { shape: 'diamond', size: 14 },
        label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-in',
        group: 'inputs',
        type: 'input',
        dataType: 'string',
        shape: { shape: 'triangle', size: 14 },
        label: { text: 'string', layout: 'outside' },
      },
    ],
  },
];

const edges: EdgeSpec[] = [
  { id: 'number-link', source: 'source', sourceHandle: 'number-out', target: 'convert', targetHandle: 'number-in' },
];

const host = document.createElement('div');
host.style.width = '100%';
host.style.height = '420px';
document.body.append(host);

requestAnimationFrame(() => render({ nodes, edges }, host));
```
```ts title="Angular"
import { Component } from '@angular/core';
import { DiagramCanvasComponent } from '@grafloria/angular';
import { portTypeRegistry } from '@grafloria/element';
import type { EdgeSpec, NodeSpec } from '@grafloria/renderer';

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

@Component({
  selector: 'app-port-example',
  standalone: true,
  imports: [DiagramCanvasComponent],
  template: `
    <grafloria-diagram-canvas
      [(nodes)]="nodes"
      [(edges)]="edges"
      style="display: block; height: 420px"
    />
  `,
})
export class PortExampleComponent {
  nodes: NodeSpec[] = [
    {
      id: 'source',
      position: { x: 80, y: 130 },
      size: { width: 160, height: 120 },
      label: 'Source',
      metadata: {
        portGroups: {
          outputs: {
            id: 'outputs',
            side: 'right',
            visibility: 'always',
            layout: { strategy: 'sideLinear', args: { padding: 18 } },
          },
        },
      },
      ports: [
        {
          id: 'number-out', group: 'outputs', type: 'output', dataType: 'number',
          shape: { shape: 'circle', size: 14 },
          label: { text: 'number', layout: 'outside' },
        },
        {
          id: 'string-out', group: 'outputs', type: 'output', dataType: 'string',
          shape: { shape: 'square', size: 14 },
          label: { text: 'string', layout: 'outside' },
        },
      ],
    },
    {
      id: 'convert',
      position: { x: 430, y: 130 },
      size: { width: 160, height: 120 },
      label: 'Convert',
      metadata: {
        portGroups: {
          inputs: {
            id: 'inputs',
            side: 'left',
            visibility: 'always',
            layout: { strategy: 'sideLinear', args: { padding: 18 } },
          },
        },
      },
      ports: [
        {
          id: 'number-in', group: 'inputs', type: 'input', dataType: 'number',
          shape: { shape: 'diamond', size: 14 },
          label: { text: 'number', layout: 'outside' },
        },
        {
          id: 'string-in', group: 'inputs', type: 'input', dataType: 'string',
          shape: { shape: 'triangle', size: 14 },
          label: { text: 'string', layout: 'outside' },
        },
      ],
    },
  ];

  edges: EdgeSpec[] = [
    { id: 'number-link', source: 'source', sourceHandle: 'number-out', target: 'convert', targetHandle: 'number-in' },
  ];
}
```
```tsx title="Qwik"
import { $, component$ } from '@builder.io/qwik';
import { GrafloriaFlow, type DiagramInstance, type EdgeSpec, type NodeSpec } from '@grafloria/qwik';
import { portTypeRegistry } from '@grafloria/element';

const nodes: NodeSpec[] = [
  {
    id: 'source', position: { x: 80, y: 130 }, size: { width: 160, height: 120 }, label: 'Source',
    metadata: { portGroups: { outputs: {
      id: 'outputs', side: 'right', visibility: 'always',
      layout: { strategy: 'sideLinear', args: { padding: 18 } },
    } } },
    ports: [
      { id: 'number-out', group: 'outputs', type: 'output', dataType: 'number', shape: { shape: 'circle', size: 14 }, label: { text: 'number', layout: 'outside' } },
      { id: 'string-out', group: 'outputs', type: 'output', dataType: 'string', shape: { shape: 'square', size: 14 }, label: { text: 'string', layout: 'outside' } },
    ],
  },
  {
    id: 'convert', position: { x: 430, y: 130 }, size: { width: 160, height: 120 }, label: 'Convert',
    metadata: { portGroups: { inputs: {
      id: 'inputs', side: 'left', visibility: 'always',
      layout: { strategy: 'sideLinear', args: { padding: 18 } },
    } } },
    ports: [
      { id: 'number-in', group: 'inputs', type: 'input', dataType: 'number', shape: { shape: 'diamond', size: 14 }, label: { text: 'number', layout: 'outside' } },
      { id: 'string-in', group: 'inputs', type: 'input', dataType: 'string', shape: { shape: 'triangle', size: 14 }, label: { text: 'string', layout: 'outside' } },
    ],
  },
];

const edges: EdgeSpec[] = [
  { id: 'number-link', source: 'source', sourceHandle: 'number-out', target: 'convert', targetHandle: 'number-in' },
];

export default component$(() => (
  <div style={{ height: '420px' }}>
    <GrafloriaFlow
      defaultNodes={nodes}
      defaultEdges={edges}
      onInit$={$((instance: DiagramInstance) => {
        portTypeRegistry.registerAll([
          { name: 'number', color: '#2563eb', compatibleWith: ['number'] },
          { name: 'string', color: '#9333ea', compatibleWith: ['string'] },
        ]);
        instance.renderNow();
      })}
    />
  </div>
));
```
```vue title="Vue"
<script setup lang="ts">
import { GrafloriaFlow } from '@grafloria/vue';
import { portTypeRegistry } from '@grafloria/element';
import type { EdgeSpec, NodeSpec } from '@grafloria/vue';

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

const nodes: NodeSpec[] = [
  {
    id: 'source',
    position: { x: 80, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Source',
    metadata: {
      portGroups: {
        outputs: {
          id: 'outputs', side: 'right', visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-out', group: 'outputs', type: 'output', dataType: 'number',
        shape: { shape: 'circle', size: 14 }, label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-out', group: 'outputs', type: 'output', dataType: 'string',
        shape: { shape: 'square', size: 14 }, label: { text: 'string', layout: 'outside' },
      },
    ],
  },
  {
    id: 'convert',
    position: { x: 430, y: 130 },
    size: { width: 160, height: 120 },
    label: 'Convert',
    metadata: {
      portGroups: {
        inputs: {
          id: 'inputs', side: 'left', visibility: 'always',
          layout: { strategy: 'sideLinear', args: { padding: 18 } },
        },
      },
    },
    ports: [
      {
        id: 'number-in', group: 'inputs', type: 'input', dataType: 'number',
        shape: { shape: 'diamond', size: 14 }, label: { text: 'number', layout: 'outside' },
      },
      {
        id: 'string-in', group: 'inputs', type: 'input', dataType: 'string',
        shape: { shape: 'triangle', size: 14 }, label: { text: 'string', layout: 'outside' },
      },
    ],
  },
];

const edges: EdgeSpec[] = [
  { id: 'number-link', source: 'source', sourceHandle: 'number-out', target: 'convert', targetHandle: 'number-in' },
];
</script>

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

When the canvas mounts, you see two labelled nodes, four always-visible ports, and the initial number-to-number connection. The source and target groups inherit their side and `sideLinear` layout; each port supplies its own glyph, label, and type. In every sample, the palette colors number ports blue and string ports purple. Drag `number-out` to `string-in` to see the mismatch refused; both registrations allow only the same named type.

## Port fields that shape the result

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `ports` | [`PortSpec`](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/grafloria-renderer-instance-portspec#portspec)[] | Omitted: four deterministic default ports | Declares the node's own connection points. |
| `shape` | Shape object | Circle | Chooses `circle`, `square`, `diamond`, `triangle`, or an SVG `path`; `size` sets the glyph box in pixels. |
| `label` | Label object | No label; when present, `layout` defaults to `outside` | Adds text near the port. Choose `inside`, `outside`, `orthogonal`, or `radial` placement. |
| `group` | `string` | No group | Inherits shared settings from the same id in the node's `metadata.portGroups`; a port's own settings override group values. |
| `metadata.portGroups` | Node metadata object | No groups | Defines shared port settings such as side, visibility, shape, and layout. A `sideLinear` layout distributes group members along that side. |
| `dataType` | `string` | Untyped | Associates the port with a registered type for glyph colour and connection compatibility. |

The default visibility mode shows ports on hover. The examples set each group's visibility to `always` so the ports and their labels appear immediately. `shape: 'path'` accepts caller-provided SVG path data; use it when the built-in glyphs do not distinguish your port.

## See the port behaviors

The [port shapes demo](https://grafloria.com/demos/ports/port-shapes.html) compares circle, square, diamond, triangle, and custom-path glyphs as distinct SVG primitives.

The [port labels demo](https://grafloria.com/demos/ports/port-labels.html) shows labels placed inside, outside, and orthogonal to their glyphs.

The [port groups and layouts demo](https://grafloria.com/demos/ports/port-groups-and-layouts.html) compares a side column, a line segment, and an ellipse spread.

The [typed ports demo](https://grafloria.com/demos/ports/typed-ports.html) registers number and string types, then shows a matching connection and a mismatched target.

## Pitfalls

- If the node spec omits `ports`, it keeps the four deterministic defaults; add a `ports` array to replace that set with your declared ports.
- Put shared configuration under the node's `metadata.portGroups` and match each port's `group` value to that entry's id. A port-level value takes precedence over the inherited group value.
- Register named data types to assign colours or declare compatibility beyond exact matches. Compatibility is directional when `compatibleWith` is used; an untyped endpoint remains unconstrained.
- A `dataType` alone does not pick a colour. Register a color for the type, then the renderer uses it for that type's port glyph.

## Related

- [Validate connections](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/validate-connections) for connection rules beyond a port's direction and data type.
- [Handle connection interactions](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/handle-connection-interactions) for responding to connection gestures.
- [The diagram model and document](https://bench-grafloria-6l.atloria.app/p/bench-grafloria-6l-dqcsEdUbfS/developer/graph-model-and-document) for how diagram specs become the live model.
