Plank help · updated 2026-09-17
Workspace scripts & the sidebar
How a small @plank-integration header on a workspace script makes it appear, grouped by provider, in your Integrations sidebar.
Agents: fetch the raw markdown of this page at /en/help/workspace-scripts.md
Workspace scripts & the sidebar
Your workspace has an Integrations section in the sidebar. It's the one place that gathers everything the agent can run against an outside service — your automation scripts, plus the credentials and connections behind them — grouped by which provider they talk to (Google, Stripe, Slack, OpenAI, and so on).
A script shows up there once you add a short @plank-integration header to the top of it. That header is the opt-in: it tells Plank "this file is an integration, and here's the provider it belongs to."
What appears in the sidebar
The Integrations section is organized by provider. Under each provider you'll see the scripts (tools) that talk to it, grouped by service, alongside any saved credentials and connections for that provider. A provider row only appears if at least one thing attributes to it — there are no empty rows.
A script you save under the workspace's scripts/ folder appears in the sidebar automatically within a few seconds — no page reload needed. (A headered script saved outside a scripts/ folder still shows up, but may only appear after you refresh the page. The live update applies to the workspace you currently have open.)
If nothing has been set up yet, the section shows a short "no integrations" message with an Explore link. The + next to the section header (and that link) starts a new chat seeded with a setup prompt, so the agent can walk you through connecting a service and writing the script for you.
The @plank-integration header
To put a script in the sidebar, add a comment block at the top. Use # for Python, Shell, and Ruby; use // for TypeScript, JavaScript, .mjs, and Go. The marker has to appear within the first 10 lines of the file.
The fields are:
provider— the external service this script talks to (e.g.google,stripe,openai). This is what groups the script in the sidebar.service— an optional sub-grouping under the provider (e.g.gmail,calendar). Defaults todefaultif you leave it off.name— an optional display name for the script. Falls back to the filename.description— an optional one-line description.requires— an optional list of what the script needs to run, askind:valueitems (see below).
Every field is optional except that, in practice, you want a provider so the script can be grouped. A partial header is fine.
Copy-paste example (Python)
# @plank-integration
# provider: google
# service: gmail
# name: send_gmail
# description: Send a Gmail message.
# requires:
# - cred: GOOGLE_API_KEY
# - file: scripts/google/credentials.json
# … rest of your script
Copy-paste example (TypeScript)
Same idea with // comments. If you'd rather keep it on one line, you can put the metadata as JSON right after the marker:
// @plank-integration {"provider":"openai","service":"chat","requires":["cred:OPENAI_API_KEY"]}
Both forms are read the same way. Use whichever is more comfortable.
The requires list
Each item is a kind:value string telling Plank what the script depends on:
cred:NAME— a credential stored under that name (e.g.cred:GOOGLE_API_KEY).file:RELATIVE_PATH— a file that must be there (e.g.file:scripts/google/credentials.json). It does not have to be a credential: a settings file the script can't run without —file:scripts/1c/mapping.json, say — belongs here too, and Plank checks it the same way, by looking for it on disk.
Plank uses these to show whether the provider is fully connected or still needs something. Item kinds it doesn't recognize are simply ignored, so a typo won't break the script.
Where credentials live
Credentials belong inside the current workspace, next to the scripts that use them:
- OAuth client credentials →
scripts/<provider>/credentials.json - OAuth tokens →
scripts/<provider>/token.json, with an ISO-8601expiryfield (and therefresh_tokenthe SDK needs to renew itself)
This is what keeps accounts separate: you can connect a personal Google account in one workspace and a work account in another, and the assistant never quietly falls back to the other one's token when this workspace's is missing — it tells you it's missing instead. That's the mistake worth preventing: a work workspace acting as your personal account without you noticing.
It is a rule the assistant follows, not a wall in the software. All of your workspaces are open to you, and to the assistant working on your behalf — so asking it outright to use a file or run a script from another of your workspaces is fine, and it should just do it and tell you which workspace it used. What it won't do is reach across on its own initiative.
One exception, and it's deliberate: when the assistant is woken by an automation — an inbound Telegram or WhatsApp message hitting a webhook trigger — it stays inside that one workspace. Nobody is there to okay it, and the message that woke it can come from anyone who can write to that chat.
Private chats and shared chats don't share a home folder
A private chat runs on your own machine; a shared team chat runs on the workspace's machine. Both see the same workspace files, but each has its own /home/coder. A sign-in done with a command-line tool (most save their login under ~), a pip install --user, or anything else kept in the home folder in one kind of chat does not exist in the other. Anything a later chat needs goes under scripts/<provider>/ — the one place both see.
Script dependencies live next to the script
When a script needs a Node package that isn't preinstalled, install it into the script's own folder: npm install --prefix scripts/<provider> <package>. That writes scripts/<provider>/package.json and scripts/<provider>/node_modules onto workspace storage, where every chat sees them and they survive the machine being recreated. (Python packages from pip install --user live in the home folder, so a shared chat may need to install them again.)
Never install into .opencode/node_modules. That folder is the assistant's own plugin tree, supplied by the machine image: anything added there exists on one machine only and disappears when it is recreated, and .opencode/package.json is overwritten on every start. A script that imported a package from there broke twice before this was understood.
A token that's already there is the connection
If scripts/<provider>/ already holds a token, the integration is connected until a real call says otherwise — even if you are in a different kind of chat than the one that set it up. Run one cheap read-only call with it before re-authorizing. And never overwrite an existing token file in a different format: other scripts in the workspace read that file, and rewriting it in the shape your new client prefers breaks them. If you genuinely need a second token, save it under its own name (for example token-<account>.json).
An older setup with the token somewhere else
Some workspaces were set up before this convention and keep the token outside scripts/ — typically /home/coder/.config/<provider>/<workspace>/token.json. That token is still this workspace's, it still works, and the right move is to keep using it where it is. An older location is not a leak, and not a disconnected integration.
Re-authorizing from scratch is only warranted when the provider rejects the credential itself — invalid_grant, a revoked grant, or a 401 that survives a token refresh. A quota or permission 403, a disabled API, or missing scopes on an otherwise valid grant are different problems and a fresh sign-in won't fix them. If a token file simply isn't where you expected, look for the path the script actually reads before concluding anything is broken.
Don't relocate it on your own initiative. /home/coder is private to one user, while the workspace is shared with everyone you've invited — so moving a credential into scripts/ publishes one person's Google (or 1C, or Stripe) access to every member of that workspace. In a solo workspace that's harmless tidying; in a shared one it's a surprise nobody asked for. If you want it moved, say so and the assistant will tell you what becomes visible before it touches anything. Note that such a path sits in one person's private home folder, so a shared team chat can't see it (see above) — there, say the token is out of reach rather than re-authorizing over it.
The sidebar understands the older layout too: a file: requirement written as an absolute path (or ~/…) under that workspace's own ~/.config/<provider>/<workspace>/ directory is checked on disk where it actually points, so the integration shows as connected rather than incomplete. Paths outside that directory are deliberately not checked — otherwise a script header could be used to probe another member's private files.
How scripts get grouped
Plank groups every script by its provider, then by service underneath it. Two scripts that both declare provider: google land under the same Google row, even if they live in different folders. Provider names are matched case-insensitively, so Google and google collapse into one row.
There are two ways a script's provider gets decided:
- The header. If your script has a
@plank-integrationheader with aprovider, that wins — and the script can live anywhere in the workspace. - The folder path, only inside
scripts/. If a script sits atscripts/<provider>/<service>/…(for examplescripts/google/gmail/send.py), Plank can infer the provider and service from the path even without a header.
A loose script directly under scripts/ (like scripts/fetch.py) with no header won't be attributed — there's no folder to read the provider from, so add a header. And anywhere outside scripts/, a header is required: that's the opt-in that keeps ordinary project files (an app's index.ts, a build script) from showing up as fake integrations. Build and type config files (like vite.config.ts or *.d.ts) are never treated as integration scripts.
A reliable convention that works either way: keep integration scripts under scripts/<provider>/<service>/ and give them a header. The agent's setup flow follows exactly this pattern.
Running these scripts
The sidebar is about discovery and grouping — it shows what integrations exist and whether they're connected. Running them happens elsewhere:
- In chat. Tapping a script in the sidebar drops its path into the chat input, so you can ask the agent to run it.
- From an HTML dashboard. A script can also expose a button on an HTML dashboard so the user can run it with one click. That's a separate opt-in — the
@plank-buttonheader — covered in Interactive HTML dashboards. The two headers are independent: a script can carry one, both, or neither.
So @plank-integration decides where a script appears and what it connects to; @plank-button decides whether a dashboard can run it directly.