Skip to content

ts storage-provider

Show and manage a storage provider — the server-wide registration of a storage backend that workspace storage scopes are mounted from.

Usage

text
ts storage-providers
ts storage-provider <slug>
ts storage-provider <slug> <property>
ts storage-provider new --file=<path>
ts storage-provider set <slug> <property> <value>
ts storage-provider set <slug> --file=<path>
ts storage-provider delete <slug> [--force]

Description

A storage provider is one backend the deployment knows how to talk to: OneDrive (storageType 0), Dropbox (1), Google Drive (2), Filr (3), or a file system path (4). Registering a provider does not give anyone storage — it gives users and administrators something to connect to. A connection made against a provider is then mounted into a workspace by ts storage-scope.

The identifier everywhere is the provider's slug. ts storage-providers lists the slug, the display name, the type, whether the type supports OAuth at all, and whether users can connect themselves; the built-in help spells the positional <id>, but the value is always the slug.

A provider carries typed fields plus type-specific configuration, so you administer it as a JSON document rather than through a flag per field. ts storage-provider <slug> prints the document, and set consumes that same shape. new and set read the document from --file; pass - to read standard input instead. storageType is fixed at create — a set that names a different one is ignored, not applied, so changing a provider's type means creating a new one.

For a single field there is a shorter path. ts storage-provider <slug> <property> reads one field, and ts storage-provider set <slug> <property> <value> writes one. The two write forms are complementary rather than alternatives: the accessor changes one field and leaves the rest of the document as it was, while --file replaces the whole document. Reach for the accessor for a one-field change — renaming a provider, correcting a fileSystemPath, turning uploadInBackground off — and for --file when you are editing several fields at once or supplying a field that no read returns.

Property names are kebab-case and map to the document's camelCase key, so file-system-path addresses fileSystemPath. A dash capitalizes exactly one following letter, which reads badly against this document's OAuth fields — hasOAuthConfiguration is has-o-auth-configuration in kebab-case. An exact document key is matched first, so spelling those two out as hasOAuthConfiguration and oAuthClientId also works. Values are typed from the field being replaced — storageType and connectionMode stay numbers, uploadInBackground stays a boolean — so you never have to know a field's wire type. A field that is currently null, such as url on a provider that does not use one, is the exception: there is nothing to infer a type from, so the value goes in as a string, which is what these fields hold anyway.

A structured field has no accessor form. A property holding an object or an array is refused with a pointer at --file, and so is a property the document does not carry. Both refusals happen before anything is written, so a typo cannot half-apply. Two consequences are worth knowing. The accessor cannot rotate a secret: oAuthClientSecret is in no read, so the accessor sees no such field and refuses it — secret rotation stays a one-field --file write. And whatever a read hides from you, the accessor cannot address either: on a non-admin read the dropped fields are simply absent, so naming one is refused as unknown rather than written blind.

The accessor is a read-modify-write: it fetches the document, changes the one field, and sends the whole document back. That is safe here because the document round-trips untyped, so a field this version of ts does not know about is preserved rather than dropped. It does not widen what the server will accept, though — the server ignores storageType, id, created, and the four capability flags on a write, so naming one of those gets past the CLI, reports success, and changes nothing.

Secrets never come back. The OAuth client secret is stored but is not part of any read: oAuthClientId is printed, oAuthClientSecret is not, for administrators and service callers alike. On write, omitting oAuthClientSecret keeps the stored value and including it replaces it. That makes print → edit → set safe, and it makes print → new lossy: the new provider is created with an empty secret, so hasOAuthConfiguration comes back false and nobody can connect through it until you supply one. The printed hasOAuthConfiguration flag is the quickest way to spot a provider in that state.

A non-admin read returns a short descriptor. The full document — url, oAuthClientId, directoryId, fileSystemPath, fileSystemPathMode, uploadInBackground — goes to any administrator, whether that is a service token or a Server Administrator's own ticket, so --local is not required to read a provider back. Everyone else gets slug, name, storageType, connectionMode, and the four capability flags. Writes require an administrator too, so read and write now agree: an administrator can round-trip a provider remotely. The accessor follows the read, so a field the short descriptor omits is refused as though the provider did not have it — which only affects non-administrators, who cannot write anyway.

The server validates the document before storing it. Slugs may contain only letters, digits, dashes, underscores, and dots, cannot consist only of dots, and cannot be create or test, both of which are reserved for routes. FileSystem providers require a fileSystemPath and must stay at connectionMode 1 (Global) — per-user file system storage is expressed with fileSystemPathMode 0 instead, which appends the caller's login as a child of the root. OneDrive, Dropbox, and Google Drive require an oAuthClientId; Filr requires an oAuthClientId and a url.

ts storage-provider delete prompts unless you pass --force, and it is a soft delete: the row is marked, not removed. The slug stays reserved afterwards, so you cannot register a new provider that reuses a deleted one's slug — pick a different slug rather than trying to recycle it.

Options

FlagDescriptionDefault/ValuesNotes
--fileJSON document to send.PATH, or - for standard inputRequired by new. set needs it only for the whole-document form, and for oAuthClientSecret.
--force, -fDelete without confirming.delete only. Also required when running non-interactively.
--localTarget the install on this machine with a service token.For administering the box you are on; not needed to read the full document.

Output

print writes the document and nothing else, so it pipes cleanly:

json
{
  "id": "onedrive",
  "slug": "onedrive",
  "name": "OneDrive",
  "storageType": 0,
  "url": null,
  "oAuthClientId": "8f1c9a02-3d55-4a17-b0e2-5c9e1a7d4f60",
  "directoryId": "common",
  "fileSystemPath": null,
  "fileSystemPathMode": 0,
  "uploadInBackground": true,
  "connectionMode": 0,
  "created": "2026-05-04T18:22:31.44Z",
  "supportsOAuth": true,
  "hasOAuthConfiguration": true,
  "canUserConnect": true,
  "canCreateGlobalConnection": false
}

id repeats the slug, and created and the four capability flags are computed by the server — all of them are ignored when you send the document back. Under --format=json the document is wrapped in the standard result envelope as result.entity, which is not what set consumes. Use the default text output for round trips.

A single-field read prints the bare value — OneDrive, not "OneDrive" — so it drops straight into a shell variable or an if. Under --format=json that value comes back in the envelope under the document's own key, result.hasOAuthConfiguration.

Examples

bash
# Which backends are registered, and can users connect themselves?
ts storage-providers

# One provider in full, from the server itself
ts storage-provider onedrive

# Just one field — is this provider usable yet?
ts storage-provider onedrive hasOAuthConfiguration

# Change one field, no file
ts storage-provider set onedrive name "OneDrive (HQ tenant)"

# Several fields at once: the document round trip
ts storage-provider onedrive > onedrive.json
$EDITOR onedrive.json
ts storage-provider set onedrive --file=onedrive.json

# Register a file system provider from a here-doc rather than a file
cat <<'JSON' | ts storage-provider new --file=-
{ "slug": "hq-home", "name": "HQ Home Directories", "storageType": 4,
  "fileSystemPath": "\\\\hq-nas\\home", "fileSystemPathMode": 0, "connectionMode": 1 }
JSON

# Rotate an OAuth client secret without touching anything else. No read returns the
# secret, so the accessor has no field to name for it — this one needs --file.
echo '{"oAuthClientSecret":"<new-secret>"}' | ts storage-provider set onedrive --file=-

# Retire a provider
ts storage-provider delete dropbox --force

# Gotcha: a printed document is not a create template. It has no
# oAuthClientSecret, so a provider made from one has empty credentials and
# 'hasOAuthConfiguration': false, and reusing its slug is rejected anyway.

Exit Codes

0 on success. -1 when the server rejects the document — an invalid or reserved slug, a duplicate slug, a FileSystem provider with no path or with connectionMode 0, an OAuth provider with no client id, a Filr provider with no URL — and when new is run without --file, when set is run with neither --file nor a property and value, or a delete is declined or run non-interactively without --force. -1 also covers the accessor's own refusals: a property that is not a field of the provider, and a property holding an object or array. -4 when no account is signed in. -5 when the account is not a Server Administrator, which affects new, set, and delete but not reads. -6 when no provider has that slug, and when a single-field read names a property the document does not carry.

  • ts storage-scope — Mounts a connection made against a provider into a workspace.
  • ts workspace — The workspaces that storage scopes belong to.
  • ts settings — Server-wide settings, including the domain URL that file system scope URLs are built from.