Skip to content

Create a custom source or destination

The guided form for a source or destination exposes the settings most people need. When it doesn’t fit, write the receiver’s or exporter’s settings yourself. That covers two cases:

  • The component has no LinkMesh template. The collector has the receiver or exporter compiled in — sqlquery, say — but LinkMesh offers no template for it. You enter the type yourself.
  • A template hides an option. The component is in the picker, but the setting you need — a TLS field, a retry knob, a protocol option — isn’t on its form.

A custom body configures a component that is already compiled into your collector. It cannot add one the collector does not have.

You write the body; LinkMesh writes the name

Section titled “You write the body; LinkMesh writes the name”

This is the one rule to keep in mind while writing. You supply the component’s settings — what goes under its entry in the collector config. LinkMesh supplies the section (receivers: or exporters:) and the type/name: line.

LinkMesh names each component after the collector attachment it belongs to: <type>/input_src_<id> for a source, <type>/output_dst_<id> for a destination. The pipeline canvas attributes throughput to a source or destination by exactly that name, so a component with any other name would carry data but show nothing on the canvas. That is why there is no field for it, and why a body that starts with its own type/name: header is refused.

For an otlp receiver, you write:

protocols:
  grpc:
    endpoint: 0.0.0.0:4317
    max_recv_msg_size_mib: 16

and the collector receives:

receivers:
  otlp/input_src_3f9a1c2e:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
        max_recv_msg_size_mib: 16

The Name you give the source or destination is how it is listed in LinkMesh. It is not the component name in the collector config.

Open Sources or Destinations (under Data & Routing in the sidebar) and click + New Source or + New Destination. Choose Raw YAML in the Mode section. Switching modes discards what you have entered, so decide first.

Raw YAML mode, Details step: sqlquery, a receiver LinkMesh has no template for, typed into the field below the list, with the one signal type it produces picked.
  1. Details — the receiver type or exporter type, a name, an optional description, and the signal types.

    Pick the type from the list, or type it into Or enter a receiver type LinkMesh has no template for (exporter type for a destination). Use the type exactly as the collector names it, such as sqlquery: a letter, then letters, digits and underscores.

    Signal types are not read from the body. They decide which pipelines the source or destination can be placed in, and for a type LinkMesh has no template for they are all LinkMesh knows about what it produces or accepts. Pick exactly the signals the component handles, and at least one. If you already have a custom template, Start from a custom template fills these in, and the body too.

  2. Body — the settings, as YAML. The editor checks the syntax as you type and shows the parser’s message when it fails. The body must be a mapping of settings (key: value lines), not a single value or a list.

    A reference to a collector extension — auth.authenticator, any storage key — must be a ${ext:<label>} token naming an extension declared on the collector or its group. LinkMesh replaces it with the extension’s name. A literal extension name is refused: LinkMesh names extensions itself, so a typed-in name would point at nothing and the collector would stop at start. Attaching is refused on a collector where the label is not declared. See Add a collector extension for declaring one and the full reference rules. For example, a destination that authenticates through an extension declared with the label idp:

    endpoint: https://otlp.partner.example:4318
    auth:
      authenticator: ${ext:idp}

    Secrets work as in the guided form: write ${secret:<name>} and the value is resolved when the config is generated. The value never appears in the body:

    endpoint: https://otlp.partner.example:4318
    headers:
      X-Api-Key: ${secret:partner_api_key}
  3. Review — Generated OTel Config shows the component exactly as the generator writes it, key and body. The _00000000 suffix on the name is a placeholder. Each attachment gets its own. Below it, Checks shows which checks ran and what each found.

The Body step of a custom source: the receiver's settings as YAML, checked as you type. LinkMesh writes the receivers section and the component name.
The Review step: the component exactly as the generator writes it, with a placeholder name, and which checks ran and what each found.

Click Create Source or Create Destination. Attach it the way you attach any other: activate the source on a collector or group, and activate the destination where the route that should use it runs, then add it to that route.

Deploy to Fleet for a custom source: Grafana Alloy collectors are listed but cannot be selected, and say why; otelcol collectors can.

A route can only use a destination that is activated where the route runs: on the route’s collector or on a group that collector belongs to, and for a route on a group, also on the group’s members. Saving a route that names any other destination activation is refused, with a message naming it.

Example: a receiver LinkMesh has no template for

Section titled “Example: a receiver LinkMesh has no template for”

The sqlquery receiver turns the result of a SQL query into metrics. otelcol-contrib contains it, and LinkMesh has no template for it. On the Details step, type sqlquery into Or enter a receiver type LinkMesh has no template for and pick metrics as the only signal type. The body:

driver: postgres
datasource: "host=orders-db port=5432 dbname=orders sslmode=require"
collection_interval: 60s
queries:
  - sql: "SELECT status, count(*) AS n FROM orders GROUP BY status"
    metrics:
      - metric_name: orders.count
        value_column: n
        attribute_columns: [status]
The Body step for the sqlquery receiver: its settings as YAML, without the receivers section or the component name.

On the Review step, two of the four checks show as skipped. LinkMesh has no settings list for sqlquery, so it cannot check the keys. And no collector is chosen yet, so it cannot check that one contains the component. Both are checked by the collector, or at attach time, instead.

The Review step for sqlquery: syntax and shape passed, settings and component presence skipped, each with its reason.

Example: an option the template does not offer

Section titled “Example: an option the template does not offer”

The guided OTLP HTTP form has no field for the certificate authority the exporter should trust. A partner gateway whose certificate is signed by the partner’s own CA needs one. A custom destination of type otlphttp sets it:

endpoint: https://otlp.partner.example:4318
compression: zstd
tls:
  ca_file: /etc/otelcol/certs/partner-ca.pem

The file must exist on every collector host the destination runs on. The checks cannot see the host. A missing file is found by the collector when it starts the config: see When the collector cannot load it.

The Body step of a custom destination: the exporter's settings as YAML, with Durable Delivery set beside the body, not in it.
The Review step of a custom destination: the exporter exactly as generated, with a placeholder name, and the checks. settings passed against the otlphttp exporter's settings list.

What the checks catch, and what they cannot

Section titled “What the checks catch, and what they cannot”

LinkMesh does not have the configuration schema of every collector component, so it cannot prove a body is correct. It runs four checks and tells you what each one did:

Check Catches When it runs
syntax A body that isn’t valid YAML Always
shape A scalar or list instead of a mapping; a type/name: header; an extension named literally instead of by ${ext:…} token; a key LinkMesh writes itself (a non-list operators on a log receiver) Always
settings A key the component has no such setting for — a misspelled key, or a setting at the wrong level. The collector refuses its whole config over one unknown key For the receiver and exporter types LinkMesh lists. For a type you entered yourself it is shown as skipped: LinkMesh has no settings list for it
component presence A component type the collector doesn’t contain When you attach it, and only when LinkMesh knows which components that collector contains. On the Review step no collector is chosen yet, so it is shown as skipped, with the reason. See When you attach it

A failed check blocks Create and names what is wrong. The checks after it are shown as not run. The same checks run again when you save, so a body sent through the API directly is held to the same rules.

A failed check on the Review step: settings names the misspelled key, the receiver and collector version it checked against, and the keys accepted at that level. Create is disabled.

Passing every check that ran means nothing obviously wrong was found. It does not mean the collector will accept the body. A setting with the wrong kind of value, an option your collector’s version doesn’t have, or a signal type the component doesn’t handle is found by the collector, not by LinkMesh. For a type you entered yourself, so is a misspelled key.

Attaching the source or destination to a collector checks one more thing: whether that collector contains the component. LinkMesh answers from the components the collector reported, or, when it has not reported any, from the component list LinkMesh ships for the collector’s exact version.

  • The collector contains it — the attach goes ahead.
  • The collector does not contain it — the attach is refused, and the message names the collector, its version and the missing component.
  • LinkMesh cannot tell — the collector reported no components and runs a version LinkMesh has no list for. A type LinkMesh lists attaches as usual. A type you entered yourself is refused, because nothing confirms the collector has it.
Assigning a custom destination to a group whose collector does not contain the exporter: refused, naming the collector, its version and the missing component.

The same check applies to every way of attaching: to one collector, to several at once, on the canvas, to a group, and when a collector joins a group that already carries the custom body. On a bulk attach, only the collectors without the component are refused; the others get it.

The collector loads its configuration as one document. If one component in it cannot be loaded, the collector refuses the entire config: every source, processor and destination on it, not just your custom block. A key the component doesn’t have, a file that doesn’t exist on the host, a port that is already in use: each one stops the collector at start.

When that happens, LinkMesh sends the collector the last config it applied successfully, and the collector is running again within seconds. On the collector’s Events tab you see, newest first:

  • Agent applied the server-pushed config: the previous config, running again.
  • The pushed config failed to start (…); the collector was rolled back to its last good config: the text in brackets is the collector’s own error. It names the component and the cause, such as the file that does not exist.
  • Agent rejected the server-pushed config: the collector refused the new config.

The collector’s header shows Config drift until the next push.

A collector's Events tab after a custom destination named a CA file that does not exist: the rejection, the rollback with the collector's error naming the file, and the previous config applied again.

The destination that caused it reads misconfigured on the Destinations list, and its badge names the file: Cannot load partner-ca.pem. Hover over the badge for the collector’s full message. It stays that way after the rollback, until the file exists or the body no longer names it.

A custom destination whose body names a CA file that does not exist on the collector: its status reads Cannot load partner-ca.pem.
  1. Read the generated config on the Review step. Compare each key against the component’s own documentation for the collector version you run. A wrong key is far cheaper to find here.

  2. Attach it to one otelcol collector first. Check the collector’s Events tab:

    • Agent applied the server-pushed config, with no rejection before it: the collector runs the config with your body in it.
    • Agent rejected the server-pushed config: the collector refused it.
    • The pushed config failed to start (…); the collector was rolled back to its last good config: follows a rejection. The collector is back on its previous config, so your body is not running there. The text in brackets is the collector’s own error, naming the offending key or file. Fix the body or the host, then save or Reapply Config to try again.
  3. Then widen it to more collectors or a group.

A custom source or destination shows throughput like any other, as long as its component reports the standard OpenTelemetry Collector counters: records accepted for a receiver, records sent for an exporter. Most upstream components do. LinkMesh reads these counters from the collector’s own telemetry. It does not measure the data itself.

A source shows its rate inside the collector’s card on the topology canvas. Expand the card: each source has a row, and each row shows that source’s own rate. A row that shows — has nothing reported for that source. The card’s in line is the sum across all of the collector’s sources.

An expanded collector card: each source row shows that source's own rate, a dash where nothing is reported for it, and the in line sums them.

A destination shows its rate on the edge from the collector to the destination, and in the route sparkline. When the collector sends its own telemetry but nothing for your exporter, the edge reads not reported instead of a number. An edge from a collector group reads not reported when at least one member sends its own telemetry and none reports your exporter. An edge that shows — means the collector has not sent any telemetry yet, for any component.

Two custom destinations on one collector: the prometheus exporter reports its counters and its edge shows a rate; the nop exporter emits none, so its edge reads not reported.

When a custom source or destination shows no number:

  • Just attached — the first numbers usually arrive within about two minutes. Rates need a steady stream of data; a single burst reads as nothing.
  • Still nothing after that — the component does not emit the standard counters. Data can still be flowing. Check the destination itself, or the collector’s own logs, to confirm delivery.

Custom bodies do not run on Grafana Alloy collectors, so there is no throughput to show there.

A custom source or destination stays in Raw YAML mode. Edit opens the same three steps, starting at Details, with the type locked and no mode switch. Create a new one if you need a different type.

A changed body applies everywhere the source or destination is attached, so an edit that breaks the body stops every collector it runs on, each for the few seconds before it is rolled back. Edit, check the Review step, and watch one collector’s Events tab for Agent applied the server-pushed config, with no rejection before it, before you assume the rest are fine. Renaming it does not touch the body.

Clone copies the body with everything else.

A custom destination supports Durable Delivery, set beside the body rather than in it. See Resume after restart.