Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/plugins/apis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ Requires `storage.upload`.

### `owncast.fs.*`

A private, sandboxed filesystem at `data/plugin-data/<your-slug>/`. Unlike `owncast.storage.upload`, these files stay server-side: they're never served over HTTP. Paths are relative to your sandbox root. The host confines every path to your own directory (a plugin cannot read another plugin's files, and `../` or absolute paths collapse back inside the sandbox). Parent directories are created as needed on write.
A private, sandboxed filesystem at `data/plugin-storage/<your-slug>/files/`. Unlike `owncast.storage.upload`, these files stay server-side: they're never served over HTTP. Paths are relative to your sandbox root. The host confines every path to your own directory (a plugin cannot read another plugin's files, and `../` or absolute paths collapse back inside the sandbox). Parent directories are created as needed on write.

| JavaScript | Python | Returns |
| ---------------------- | ---------------------- | ----------------------------------------------------- |
Expand Down
13 changes: 11 additions & 2 deletions docs/plugins/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,18 @@ Grants `owncast.storage.upload(name, data)`: upload a file to Owncast's public f

### `storage.fs`

Grants `owncast.fs.*`: a private, sandboxed filesystem at `data/plugin-data/<your-slug>/` that your plugin can read, write, list, and delete within. Useful for caches, generated data files, append-style logs, or anything you need to persist as real files rather than key/value strings.
Grants `owncast.fs.*`: a private, sandboxed filesystem at `data/plugin-storage/<your-slug>/files/` that your plugin can read, write, list, and delete within. Useful for caches, generated data files, append-style logs, or anything you need to persist as real files rather than key/value strings.

Unlike `storage.upload`, these files stay **server-side**: they're never served over HTTP. Every path is confined to your plugin's own directory: a plugin cannot read another plugin's files or escape its sandbox (`../` and absolute paths are collapsed back inside).

### `storage.sql`

Grants `owncast.sql.*`: one private SQLite database per plugin, at `data/plugin-storage/<your-slug>/db/plugin.db`. `owncast.sql.exec(sql, params?)` runs statements, `owncast.sql.query(sql, params?)` returns matching rows, and `owncast.sql.queryRow(sql, params?)` reads a single row. Reach for this instead of `storage.kv` when you need to sort, filter, or aggregate rather than just remember a value. See [`owncast.sql.*`](/docs/plugins/apis#owncastsql) for the methods in both languages, the per-call limits, and the SQL the host refuses.

The database is private to your plugin and separate from Owncast's own database. The `storage.fs` sandbox is rooted at `files/`, so `db/` is not a path `owncast.fs.*` refuses but one it **cannot express**, and the filesystem quota walk covers `files/` only, so the two quotas stay independent: the database has its own 128 MiB cap, and files written through `storage.fs` count against a separate 256 MiB quota.

Plugin databases are **not** included in Owncast's database backups, so treat the contents as rebuildable or export what matters yourself. SQL data is retained when a plugin is uninstalled, the same as its config and its `storage.fs` files, so a reinstall finds its tables where it left them. An admin who wants the space back deletes `data/plugin-storage/<your-slug>/`.

### `network.fetch`

Grants `owncast.http.fetch(url, opts?)`: synchronous outbound HTTP.
Expand Down Expand Up @@ -254,7 +262,8 @@ None of the four viewer-injection fields require `http.serve`, and neither do th
| `auth.gate` | `owncast.auth.grantSession`, `.endSession`, and the `onAuthCheck` handler: be the site's auth gate |
| `storage.kv` | Per-plugin namespaced key/value store |
| `storage.upload` | Upload files to Owncast's public file area |
| `storage.fs` | Private, sandboxed server-side filesystem at `data/plugin-data/<your-slug>/` |
| `storage.fs` | Private, sandboxed server-side filesystem at `data/plugin-storage/<your-slug>/files/` |
| `storage.sql` | Private per-plugin SQLite database at `data/plugin-storage/<your-slug>/db/plugin.db` |
| `network.fetch` | Outbound HTTP. Also requires `network.allowedHosts` |
| `events.emit` | Emit custom events for other plugins |
| `http.serve` | Serve HTTP at `/plugins/<your-slug>/*` |
Expand Down
2 changes: 2 additions & 0 deletions docs/plugins/testing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ Use `body` for text uploads. It is checked only when its value is non-empty, so

`owncast.fs.*` (the `storage.fs` sandbox) has no dedicated assertion: the runtime backs it with a real in-memory sandbox during tests, so test it the way you'd use it: drive your plugin's own endpoints (or handlers) and assert on what they return. For example, `POST` a file through your upload endpoint, then `GET` your list endpoint and assert the response includes it. The [`file-manager`](https://github.com/owncast/plugin-sdk/tree/main/examples) example does exactly this.

[`owncast.sql.*`](/docs/plugins/apis#owncastsql) works the same way. The test runner and the dev server give each plugin a real in-memory SQLite database, so there's no SQL assertion and no `given.sql`: every scenario starts with an empty database and your plugin creates its own schema on first use. Drive the handlers or commands that write, then assert on what the ones that read send back. The same statements are refused there as on a real server and the same per-call limits apply, so a scenario that passes runs the same SQL in production. The `chat-leaderboard` example ([JavaScript](https://github.com/owncast/plugin-sdk/tree/main/examples/js/chat-leaderboard), [Python](https://github.com/owncast/plugin-sdk/tree/main/examples/python/chat-leaderboard)) is tested exactly this way: chat events count messages, then `!top` and `!rank` report the standings.

Example exercising several:

```json
Expand Down