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.
Create it
Section titled “Create it”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.
-
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.
-
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: valuelines), not a single value or a list.A reference to a collector extension —
auth.authenticator, anystoragekey — 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 labelidp: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} -
Review — Generated OTel Config shows the component exactly as the generator writes it, key and body. The
_00000000suffix on the name is a placeholder. Each attachment gets its own. Below it, Checks shows 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.
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]
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.
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.
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.
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.
When you attach it
Section titled “When you attach it”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.
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.
When the collector cannot load it
Section titled “When the collector cannot load 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.
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.
Before you roll it out
Section titled “Before you roll it out”-
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.
-
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.
-
Then widen it to more collectors or a group.
Throughput on the canvas
Section titled “Throughput on the canvas”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.
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.
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.
Editing it later
Section titled “Editing it later”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.
Keeping undelivered data across a restart
Section titled “Keeping undelivered data across a restart”A custom destination supports Durable Delivery, set beside the body rather than in it. See Resume after restart.
Related
Section titled “Related”- Source — what a source is and where its activations live
- Destination — what a destination is and how routes use it
- Create a custom processor — the same escape hatch, for processors
- Add a collector extension — declare the extensions a custom body references with
${ext:<label>} - Onboard otelcol-contrib (OpAMP) — the runtime custom bodies run on
- Config not applying after save — when a change doesn’t reach the collector