Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

datasette-paper

PyPI Changelog Tests License

Collaborative document editor for Datasette. ProseMirror frontend, SQLite-backed storage in Datasette's internal database, real-time collaboration over SSE.

The paper editor: a rich-text document with headings, a table and a task list, a formatting toolbar, and a header showing the author, edit time and number of users online.

Rich text with tables and task lists, wiki-style links between papers, images (paste, drag-and-drop, or insert from the toolbar), and per-paper sharing:

A table in the editor with the floating action bar for adding/removing rows and columns and naming the table for the API. A task list with checkboxes; completed items are struck through.
Typing [[ opens an autocomplete popup listing other papers to link to. The share dialog showing people with access and their roles, plus general link access.
Hovering a link while editing reveals a tooltip with Edit, Open and Copy; Edit opens a small dialog with Text and URL fields to rewrite the link.

Insert an image by pasting, dropping a file, or using the toolbar's image button — which offers a paste area or a file upload with a live preview:

The insert-image dialog with the Upload tab active, showing a preview of the chosen image, an alt-text field, and an enabled Insert button.

And a paper index listing everyone's papers with author and last-edited time:

The paper index: a table of papers with name, creator and updated time, plus tabs for Active / Archive / Trash / Templates.

The link graph turns those [[wiki links]] into an interactive map of the workspace — force-directed and zoomable, with nodes colored by tag / state / kind (the legend doubles as a filter), sized by connections or recency, and a metadata panel for the selected paper. Every paper also gets a doc-centred ego view from its sidebar: just that paper's neighbourhood, sliceable to an adjustable link depth:

The link graph modal: a force-directed graph of papers colored by tag with a legend filter, search, and a metadata panel showing the selected paper's kind, state, links and tags. The doc-centred ego view: the focus paper ringed at the centre of its depth-1 neighbourhood, with a Depth selector to widen the slice.

Drop inline #tags anywhere in a paper's body — type # for an autocomplete of existing tags. Clicking a tag opens a results page listing every paper whose body mentions it:

Inline #tag pills in a paper's body, distinct from the document-level metadata tags. The tag results page: every paper whose body contains #roadmap, each with a mention count.

Drop an inline date anywhere — type /date (or press Cmd/Ctrl-;) for today, or /today / /tomorrow / /yesterday. Each renders a compact calendar chip that round-trips through markdown and can carry a time + timezone (shown in each reader's own zone). A date inside an unchecked task tints overdue (red) or due today (amber); checking the box clears it. Click a chip to edit it in plain language (next fri 3pm, 7/20) and pick a display format:

Inline date chips: a neutral date in prose, and in a task list a red overdue date, an amber due-today date, and a struck-through completed task whose date is neutral. The date chip's edit popup: a natural-language input with a live preview, and a labelled radio list of display formats (Default, ISO, Medium, Long, Weekday, Short) each showing its own rendered example, plus a custom strftime field.

Installation

Install in the same environment as Datasette:

datasette install datasette-paper

Quickstart

datasette --internal papers.db \
  -s permissions.datasette-paper-list true \
  -s permissions.datasette-paper-create true \
  -s permissions.datasette-paper-view true \
  -s permissions.datasette-paper-edit true

No user database is required — papers live in Datasette's internal database.

Pass --internal <path> to persist papers across restarts. Without it, Datasette uses an ephemeral tempfile for the internal DB that is deleted when the process exits. The plugin emits a startup warning when it detects this, so you don't lose your papers to a forgotten flag.

Permissions

Four actions gate access. The view/edit actions are per-paper; list/create are global.

Action Scope Gates
datasette-paper-list global The paper index page and the list endpoint.
datasette-paper-create global (also-requires list) Creating new papers.
datasette-paper-view per-paper (PaperResource) Reading a specific paper (bootstrap, SSE, document, tasks).
datasette-paper-edit per-paper (PaperResource, also-requires view) Modifying a specific paper (events, presence, rename, snapshot, share).

The plugin registers a permission_resources_sql hook that resolves per-paper view/edit grants from the _datasette_paper_doc.created_by column (owners) and the _datasette_paper_share table (explicit grants

  • link-visibility levels).

Sharing

Each paper has one of three visibility levels:

  • private — only the owner and explicitly-shared actors can access.
  • link-view — any authenticated actor with the link can view.
  • link-edit — any authenticated actor with the link can view and edit.

Plus per-actor share rows that grant a specific actor a viewer or editor role on a single paper.

The owner is whoever created the paper (created_by, captured from the actor cookie at create time). Only the owner can change visibility or mutate shares.

Profile integration

When datasette-user-profiles is installed, each person's profile page grows a Papers section (registered via its datasette_user_profile_sections hook). It lists the papers that actor created plus the ones they've recently edited, newest activity first, each badged Created / Edited. The list is filtered to the papers the viewer is allowed to see — a paper you can't open never shows up on someone else's profile. The section is populated from GET /-/paper/api/profile/<actor>/docs.

A user-profiles profile page with a Papers section listing the papers that actor created or recently edited, each badged Created or Created · edited with a relative time.

The same integration adds a TODOs section listing that person's open assigned tasks (see Task assignment below), viewer-filtered the same way.

Task assignment

A @mention inside a task item assigns it to that person; a date atom inside the item is its due date. It's pure interpretation of the document — no task ids, no assignment records, no extra editor UI. Assignment inherits down a task's subtree (a sub-task with no mention of its own takes the parent's assignee), and the first date atom wins as the due date.

The dedicated /-/paper/todos page collects a person's assigned tasks across every paper the viewer can see, bucketed by due date in the viewer's timezone — Overdue / Today / This week / Later / No due date — with a status toggle for open / done / all. It's read-only: each row links into its paper, where you check the box. Both this page and the profile TODOs section are fed by GET /-/paper/api/profile/<actor>/todos, viewer-filtered so you only ever see tasks in papers you're allowed to open.

The /-/paper/todos page: a person's assigned tasks grouped into Overdue, Today, This week, Later and No due date, each row showing a checkbox, task text, assignee chips, a due-date chip tinted red (overdue) or amber (today), a section breadcrumb and the paper it lives in.

The TODOs section on a user-profiles profile page: the person's open assigned tasks, each with a checkbox, assignee chips, a due-date chip and the paper it lives in, plus an 'All TODOs' link to the full page.

Papers as data

Paper data lives in Datasette's internal database under tables prefixed with _datasette_paper_:

  • _datasette_paper_doc — one row per paper (id, name, visibility, created_by).
  • _datasette_paper_step — append-only log of ProseMirror steps.
  • _datasette_paper_snapshot — periodic full-document snapshots.
  • _datasette_paper_share — per-actor view/edit grants.

Wire protocol

JSON API rooted at /-/paper/api/... — no per-database segment. List/create docs, bootstrap a paper, post step batches, stream updates over SSE, manage shares, render markdown / extract tasks. See CLAUDE.md for the full endpoint table.

Frontend stack

Vite + Svelte 5 + ProseMirror. Bundle outputs to datasette_paper/static/.

Development

npm install --prefix frontend
just frontend          # build the bundle
just dev               # run datasette with the plugin + permissions granted
just dev-with-hmr      # vite dev server + watchexec restart
just shots             # regenerate docs/screenshots/*.png (used in this README)

just shots is self-contained: it builds the bundle, boots a throwaway Datasette with seeded papers, drives Playwright to capture each surface, and tears the server down. The PNGs are committed, so re-run and commit when the UI changes (the diff shows what changed). Pass shot names to regenerate a subset, e.g. just shots editor tables.

Run the test layers:

just test              # backend pytest
just test-frontend     # vitest
just test-e2e          # playwright (requires built bundle)

Always invoke Python with uv run --prerelease=allow … — datasette is on a >=1a23 pre-release pin.

Metadata

Release files for datasette-paper 0.0.2a17

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for datasette-paper 0.0.2a17
File Size Uploaded
datasette_paper-0.0.2a17.tar.gz 1.0 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for datasette-paper 0.0.2a17
File Interpreter ABI Platform
datasette_paper-0.0.2a17-py3-none-any.whl Python 3 none any Details

Total release size: 1.9 MB

Release files / datasette_paper-0.0.2a17.tar.gz

Download URL datasette_paper-0.0.2a17.tar.gz
Size 1.0 MB
Tags Source
SHA-256 checksum
How to use checksums
25ce8413a609645d5cdc7478a9de590da39f9d30ecf18e58a1292ca8210dfe06
BLAKE2b-256 checksum
How to use checksums
374b95636ac9b4922b1c0bbcc75a1b30d829da7d8eec3f47842968aa61da28fa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / datasette_paper-0.0.2a17-py3-none-any.whl

Download URL datasette_paper-0.0.2a17-py3-none-any.whl
Size 904.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
844ca7b551e1d7e00e773e22e8325dabd1b4028c55c3bf5cc2fe8fd569ce9ccd
BLAKE2b-256 checksum
How to use checksums
0bbe7cf3d369bc72761de10811f9af2d0b50ac1f689ff91c2e197b24249f04a0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page