Publish Your Own Community Check Repository
Publish and connect a check repository with a PowerShell script, configuration and result view.
A community check repository is a plain web location that publishes monitoring checks. Publish one and any octoja instance can connect it, so the checks appear in the Check Library and can be assigned to devices like built-in checks. This how-to builds a complete repository with one PowerShell check that reports whether a Windows process is running.
Use a repository when you want the same check on several octoja instances — across your own tenants, or handed to customers who run their own instance. If the check only ever runs on one instance, a custom script check is less work: you write it in the browser and octoja handles the packaging for you. See Custom Script.
Before you start
- Somewhere to serve static files over HTTPS — a web server, object storage, a CDN, or a static-page host. octoja does not clone a Git repository; it fetches files over HTTP or HTTPS.
- The Check Repository Management permission on the octoja instance you connect the finished repository to. Permissions are granted through groups under Administration → Groups.
- A short vendor prefix for your check IDs, so they cannot collide with checks from other repositories. This how-to uses
acme. - A Windows device with the agent installed, to try the finished check on.
What you publish
A repository is three kinds of file: one index, then a UI manifest and a script per check version.
https://checks.example.com/
├── registry.json
└── checks/
└── acme-process-running/
└── 1.0.0/
├── ui.json
└── check-process.ps1
The repository URL you hand out later is https://checks.example.com/. octoja resolves every other path against that URL, so it must end in a slash when the repository sits in a subdirectory — https://example.com/octoja-checks/. The checks/{id}/{version}/ui.json path is fixed: octoja builds it from the id and version in registry.json, so the folder names have to match those values exactly.
Step 1 — Write the check script
octoja hands the check its configuration as base64-encoded JSON on a single line of standard input, then closes the stream. The script answers with base64-encoded JSON on standard output, wrapped in two marker lines. Save this as checks/acme-process-running/1.0.0/check-process.ps1:
$ErrorActionPreference = 'Stop'
function Write-CheckResult {
param([hashtable] $Result)
$json = $Result | ConvertTo-Json -Depth 10 -Compress
$bytes = [System.Text.Encoding]::UTF8.GetBytes($json)
$base64 = [Convert]::ToBase64String($bytes)
[Console]::Out.WriteLine('<<<OCTO_RESULT_BEGIN>>>')
[Console]::Out.WriteLine($base64)
[Console]::Out.WriteLine('<<<OCTO_RESULT_END>>>')
}
try {
$encodedInput = [Console]::In.ReadLine()
if ([string]::IsNullOrWhiteSpace($encodedInput)) {
throw 'The check received no input.'
}
$inputBytes = [Convert]::FromBase64String($encodedInput)
$checkInput = [System.Text.Encoding]::UTF8.GetString($inputBytes) | ConvertFrom-Json
$processName = [string] $checkInput.processName
if ([string]::IsNullOrWhiteSpace($processName)) {
throw 'No process name was configured.'
}
$processes = @(Get-Process -Name $processName -ErrorAction SilentlyContinue)
Write-CheckResult @{
checkResult = if ($processes.Count -gt 0) { 0 } else { 2 }
processName = $processName
processCount = $processes.Count
running = $processes.Count -gt 0
}
exit 0
}
catch {
[Console]::Error.WriteLine($_.Exception.ToString())
Write-CheckResult @{
checkResult = 3
errorMessage = $_.Exception.Message
}
exit 0
}
Three details decide whether the check works at all:
- The markers. octoja reads only the bytes between
<<<OCTO_RESULT_BEGIN>>>and<<<OCTO_RESULT_END>>>. Anything else on standard output — aWrite-Hostline, a module banner, a strayWrite-Output— is then harmless. Without the markers octoja falls back to reading the entire output as the result, and a single extra line breaks the check. - Always exit with code 0. A non-zero exit code makes octoja discard the result and report Failure, no matter what the script wrote. That is why the failure path above still ends in
exit 0— it reports the problem throughcheckResultinstead. - Diagnostics go to standard error. Use
[Console]::Error.WriteLine(…). The agent stores standard error in its own log on the device, so it is available when a check misbehaves.
Note: this is the opposite of a custom script check built inside octoja. There, octoja wraps your script and emits the markers itself, so writing them yourself breaks the result. In a repository your script is downloaded and run unchanged, so emitting them is your job.
Result status
The checkResult field carries the status. It accepts the number or the name — 2 and "Critical" are equivalent, and the field name itself is matched case-insensitively.
Keep the result small. The decoded output is capped at 1 MB; beyond that the status survives but the values are dropped. Full logs and inventory dumps do not belong in a check result.
Step 2 — Describe the check in registry.json
The index lives at the root of the repository and lists every check you offer. Save it as registry.json:
{
"version": "1.0",
"generated": "2026-08-11T12:00:00Z",
"baseUrl": "https://checks.example.com/",
"checks": [
{
"id": "acme-process-running",
"name": {
"en": "Process running",
"de": "Prozess läuft"
},
"description": {
"en": "Checks whether a Windows process is running.",
"de": "Prüft, ob ein bestimmter Windows-Prozess läuft."
},
"author": "ACME",
"icon": { "type": "component", "library": "lucide", "name": "activity" },
"category": "services",
"tags": ["windows", "process", "service"],
"platforms": ["win-x64"],
"version": "1.0.0",
"uiVersion": "ui-1",
"interval": 5,
"timeoutSeconds": 60,
"executionLocation": "Device",
"executable": {
"win-x64": {
"downloadUri": "https://checks.example.com/checks/acme-process-running/1.0.0/check-process.ps1",
"executableName": "check-process.ps1",
"fileType": "Ps1",
"executionType": "PowerShell"
}
}
}
]
}
The fields that most often go wrong:
interval is the default run interval in minutes, and executionLocation is Device for a check that runs on the agent — the default, so it can be left out. Localized text takes en, de, es, fr, it, and nl keys; always provide en, because that is the fallback when a reader's language is missing. The complete field list is in the Check Repository Reference.
Step 3 — Define configuration and result view in ui.json
The manifest declares what the user configures when assigning the check, and how the result renders. Save it as checks/acme-process-running/1.0.0/ui.json:
{
"id": "acme/acme-process-running",
"input": {
"processName": {
"type": "string",
"title": { "en": "Process name", "de": "Prozessname" },
"description": {
"en": "Process name without .exe, for example spoolsv.",
"de": "Prozessname ohne .exe, zum Beispiel spoolsv."
},
"default": "spoolsv",
"isRequired": true
}
},
"output": {
"detailRenderer": {
"type": "react-inline",
"code": "export default function ProcessRunning({ data, lang }) {\n const de = (lang || '').startsWith('de');\n const running = data?.running === true;\n const failed = Number(data?.checkResult) === 3;\n const color = failed || !running ? 'text-destructive' : 'text-green-600 dark:text-green-400';\n const label = failed ? (de ? 'Check fehlgeschlagen' : 'Check failed') : running ? (de ? 'Prozess läuft' : 'Process is running') : (de ? 'Prozess läuft nicht' : 'Process is not running');\n return <div className='rounded-lg border border-border p-3'><div className={'text-sm font-medium ' + color}>{label}</div>{data?.processName && <div className='mt-1 text-xs text-muted-foreground'>{data.processName}{running ? ' · ' + (data.processCount ?? 0) : ''}</div>}</div>;\n}"
}
}
}
Every key under input becomes a field in the assignment dialog, and arrives in the script's input JSON under exactly that name. The example declares processName, so the script reads $checkInput.processName — rename one and you must rename the other.
Common field types are string, text, integer, number, boolean, select, radio, and array. Use password for anything secret: the value is masked in the interface and stored with the assignment instead of sitting in your published files.
If the check probes a remote device rather than the machine it runs on, declare the target as a string field named host, ip, ipaddress, target, url, endpoint, or address — that makes the check assignable to network devices, where octoja hides the field and fills in the device's address on every run. Pick string, not text — despite the name, text is a static label rather than an input, and only string counts as a host key. The full rules, including the array variant and the SNMP credential keys, are in the Check Repository Reference.
The detailRenderer is optional — leave it out and the raw result fields are listed as they are. When present, its code is a small React component receiving data (your result JSON), input (the configured values), and lang. JSX is fine; it is compiled for you. Renderer code from a community repository runs in an isolated frame with no access to the octoja session, cookies, or the surrounding page, so it can display the result and nothing else.
Step 4 — Test the script locally
Try the protocol on your own machine before publishing. Run this from the folder holding the script:
$inputJson = '{"processName":"spoolsv"}'
$inputBytes = [System.Text.Encoding]::UTF8.GetBytes($inputJson)
$inputBase64 = [Convert]::ToBase64String($inputBytes)
$raw = ($inputBase64 | powershell.exe -ExecutionPolicy Bypass -File .\check-process.ps1) -join "`n"
if ($raw -match '(?s)<<<OCTO_RESULT_BEGIN>>>(.*?)<<<OCTO_RESULT_END>>>') {
[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Matches[1].Trim()))
} else {
Write-Warning 'No result block found — check what the script writes to standard output.'
$raw
}
That extraction is what octoja does with the output too. The last line should print your result JSON, for example:
{"checkResult":0,"running":true,"processName":"spoolsv","processCount":1}
Run it again with a process name that is not running, and with an empty one, to confirm you get 2 and 3 — and an exit code of 0 every time. The field order in the JSON is not fixed, only the field names matter.
Note there is no -NoProfile in that command, because the agent does not use it either — it runs powershell -ExecutionPolicy Bypass -File with Windows PowerShell. Testing without it means a machine-wide PowerShell profile that prints something on startup shows up here rather than in production. Test on a device that resembles your targets.
Step 5 — Publish the files
Copy the three files to the web server, keeping the layout from What you publish:
/registry.json/checks/acme-process-running/1.0.0/ui.json/checks/acme-process-running/1.0.0/check-process.ps1
Then confirm all three are reachable:
curl https://checks.example.com/registry.json
curl https://checks.example.com/checks/acme-process-running/1.0.0/ui.json
curl https://checks.example.com/checks/acme-process-running/1.0.0/check-process.ps1
A private repository can sit behind HTTP basic authentication across all three paths. octoja sends the same credentials when it reads the registry, fetches a manifest, and downloads a script — and it only pairs credentials with an https:// URL, except on loopback addresses such as localhost.
Step 6 — Connect the repository
On the octoja instance, go to Settings → Check repositories, click Add community repo, and enter a display name plus https://checks.example.com/. Use Check connection to confirm octoja can read the registry — it reports how many checks it found — then save. The full walkthrough, including basic-auth credentials and what each error means, is in Connect a Community Check Repository.
After the first sync — the next hourly one, or right away with Sync now — your check appears in the Check Library with a Community badge, ready to assign through a config package.
Publish an update
When the script changes, publish it under a new version:
- Pick a new version number, for example
1.0.1. - Create
/checks/acme-process-running/1.0.1/and publish the script andui.jsonthere. - Update
versionand the executable'sdownloadUriinregistry.json. - Set
generatedto the current UTC time.
The version is part of the path the agent stores the script under, and an agent that already holds that version never downloads it again. Publishing a changed script under an unchanged version therefore does not reach devices that ran the old one — they keep the copy they have. Always raise the version.
When only ui.json changes, replace it in place, change uiVersion, and set generated. Devices keep the script they already have, and the new configuration fields and result view arrive on the next sync.
octoja re-syncs connected repositories automatically once an hour. Sync now on the Check repositories page forces a refresh immediately.
Security
A repository hands executable code to every device that runs its checks, so treat publishing it as a trust relationship in both directions.
The same applies when you connect someone else's repository: only add one from a source you trust.
Troubleshooting
The connection test says the registry could not be parsed. The URL answered, but octoja found no readable registry.json directly under it. Check that the base URL ends in a slash, and fetch registry.json with curl to confirm the server returns the file rather than a directory listing or an error page.
The check is listed but has no configuration fields. The manifest could not be fetched on the first sync. Confirm that checks/{id}/{version}/ui.json resolves for exactly the id and version in the registry — a mismatched folder name is the usual cause — then use Sync now.
The check reports Failure on the device. Either the script exited with a non-zero code, or octoja could not read a result from its output. Confirm the script ends with exit 0 on every path, and that the markers are written exactly as shown. The agent keeps the script's standard error in its check log on the device.
A changed script has no effect. The version in registry.json did not change, so agents kept the copy they already had. Publish under a new version number.
The Check Library does not show the new check. Bump generated in registry.json — the hourly sync skips a repository that reports nothing newer — or use Sync now. Seeing the Check Library also needs the Monitoring Check Management permission.
For current incidents and known limitations, see Product Status & Known Limitations.
Related articles
- Check Repository Reference — every registry and manifest field, and how syncing decides what to pull.
- Connect a Community Check Repository — adding, editing, and syncing a repository.
- Custom Script — building a check inside octoja instead of hosting one.
Was this helpful?
More in Custom checks
Write result text for custom checksStill need help? Ask the team