Docs

linkcode.json

Reference for the linkcode.json workspace config file, which declares the tasks and services LinkCode can run for a project.

linkcode.json is an optional file at the root of a workspace. It declares scripts your host (the LinkCode daemon) can run on your behalf — either one-off tasks or long-running services with a live preview. LinkCode reads this file fresh every time you open or run a script, so edits apply immediately without restarting the host.

Schema

{
  "scripts": {
    "<name>": {
      "type": "task" | "service",
      "command": "string (required)",
      "port": 12345
    }
  }
}
  • scripts — an object keyed by script name. The name is what shows up in the Services list and is also used to build the script's preview hostname.
  • type"task" or "service". Optional; defaults to "task" if omitted.
    • A task runs once and exits. Its own exit code is recorded; it gets no port and no preview URL.
    • A service is kept running. LinkCode health-checks it over TCP and gives it a proxied preview URL. See Preview for how services show up in the UI.
  • command — required, non-empty. Run through your shell (/bin/sh -c) with the workspace directory as its working directory.
  • port — optional, and only meaningful for a service. Pins the service to that TCP port. If omitted, LinkCode picks a free loopback port for you. Setting port on a task is harmless but ignored — tasks never get a port.

A malformed entry (missing/empty command, an unrecognized type, an out-of-range port) is dropped on its own; the rest of the file still loads.

Environment variables

Every script LinkCode runs gets these variables injected into its environment automatically — no configuration needed:

VariableSet onValue
LINKCODE_PORTthe service itselfthe port that service is bound to
LINKCODE_URLthe service itselfthe service's own preview URL
LINKCODE_SERVICE_<NAME>_PORTevery script (tasks included)the port of the service named <NAME>
LINKCODE_SERVICE_<NAME>_URLevery script (tasks included)the preview URL of the service named <NAME>

<NAME> is the script's name, upper-cased, with any run of characters that isn't A–Z or 0–9 collapsed to a single underscore (so web-app becomes WEB_APP). A service also gets its own LINKCODE_SERVICE_<NAME>_* pair alongside LINKCODE_PORT/LINKCODE_URL, so any script — including the service itself — can find any declared service by name. Tasks only ever see the LINKCODE_SERVICE_* variables, since a task has no port of its own.

Preview URLs point at a <script>--<workspace>-<hash>.localhost address proxied through the host, on the same port the host itself is listening on (see Local data for the host's default port). The short hash is derived from the workspace's path, so two workspaces with the same name don't collide. Browsers and most OSes resolve *.localhost to your machine automatically, so these URLs work without any DNS setup.

One naming collision to be aware of: the LINKCODE_PORT injected into a service is unrelated to the LINKCODE_PORT you can set in your own shell to move the host's listen port (local data) — inside a service's environment, the injected value always wins.

Worked example

{
  "scripts": {
    "web": {
      "type": "service",
      "command": "pnpm dev",
      "port": 3000
    },
    "worker": {
      "type": "service",
      "command": "pnpm dev:worker"
    },
    "test": {
      "command": "pnpm test"
    }
  }
}
  • web is a service pinned to port 3000. Its process sees LINKCODE_PORT=3000, LINKCODE_URL=http://web--myproject-a1b2c3.localhost:19523, plus LINKCODE_SERVICE_WORKER_PORT/LINKCODE_SERVICE_WORKER_URL for its sibling.
  • worker is a service with no fixed port — LinkCode allocates one for it. Its process sees its own LINKCODE_PORT/LINKCODE_URL, plus LINKCODE_SERVICE_WEB_PORT/LINKCODE_SERVICE_WEB_URL.
  • test has no type, so it defaults to a task: it runs once when you trigger it and exits. It gets no LINKCODE_PORT of its own, but does see LINKCODE_SERVICE_WEB_* and LINKCODE_SERVICE_WORKER_* — useful if your test suite needs to hit a running dev server.

Tip

If you only need one-off commands with no port or preview, omit type entirely — every entry defaults to a task.

On this page