The guide for plugin authors
How to publish a plugin in PaperPaw's plugin store, with the form on this site. The names on this page, of files, keys, fields and checks, are the ones the site uses.
PaperPaw is in beta, and its SDK may still change before 1.0. The changelog lists each breaking change with what replaces it. The changelog
What a plugin is
A plugin is a folder with a file plugin.ini at its top. There are two kinds.
- Data plugins. Files PaperPaw reads and nothing else: translations, key maps, new defaults for settings, themes, fonts, a stylesheet for books, pictures. A data plugin runs on every device as it is.
- Code plugins. A Rust crate against the PaperPaw SDK, built into one file,
code.ppn, for each reader it runs on: the Xteink X4 and the X4 Pro. You upload these builds beside the folder, each signed with your key, and the source they were built from.
Or let the plugin workshop write it: describe the plugin, data or code, and it makes it, tries it on an emulated reader and checks it, on your computer. The plugin workshop
A code plugin runs as part of PaperPaw, at full speed and without a sandbox: it can do whatever PaperPaw can, with the reader's books, settings and network. The store says so on every code plugin's page, and the reader says so before it installs one. This is why a code plugin shows its source, is signed by its author, and is rebuilt from its source to compare.
Every plugin in the store is free for everyone. The store sells nothing and takes nothing.
How to write a plugin, with the SDK, the emulator and the sample plugins, is in PaperPaw's own guide. Writing plugins
The id
The id names the plugin everywhere: in plugin.ini, in its folder on the reader, in its address in the store and at the start of every id in its code (clock.open). It is lower-case letters, digits and hyphens, at most 40, and does not begin or end with a hyphen, such as clock or large-print. It must be free: not the id of a plugin in this site's catalogue, not another author's, and not one that was taken down.
The folder
The folder is named as the id. A folder store/ inside it holds what only the store needs: store.ini and the screenshots. The store takes it out of what readers install. A code plugin's builds are not in the folder: you upload them apart.
night-owl/ a data plugin
plugin.ini
settings.ini
i18n/de.ini
store/store.ini for the store only
store/home.png a screenshot
clock/ a code plugin
plugin.ini
i18n/de.ini
store/store.ini
store/home.png
clock-1.0.0-x4.ppn its builds, signed, beside the folder
clock-1.0.0-x4-pro.ppn
plugin.ini names the plugin. Its section [plugin] needs id, name and version; description is one sentence the store uses as the summary when store.ini gives none. Two complete examples:
# night-owl/plugin.ini: what the plugin is. It lies at the top of its folder.
[plugin]
# Lower-case letters, digits and hyphens, at most 40: the id everywhere.
id = night-owl
# The name people see, at most 60 characters.
name = Night owl
# One sentence. The store takes it as the summary when store.ini has none.
description = Dark headers and roomy rows for reading at night.
# Three numbers. A new version needs a higher one.
version = 1.0.0
# The version of the data formats its files are written in: 1 for settings
# and translations, up to 3 (menus.ini, actions.ini); docs/data-files.md.
formats = 1
# clock/plugin.ini: a code plugin's folder has the same file.
[plugin]
id = clock
name = Clock
description = A large clock on the home screen, with the date below it.
# The same version as the manifest in the plugin's code.
version = 1.0.0
# Optional: the most memory it takes while it runs, in KB, as you measured
# it in the emulator. The readers' store shows it, and on the X4 above
# 32 KB it says the plugin is tight on that reader.
heap = 40
Publishing from the Workshop
The plugin workshop makes one file with all the store needs, so there is only one thing to upload. The plugin workshop
In the Workshop, choose Publish in the menu and press Pack. It checks the plugin; a plugin with code it builds for the readers and signs with your author key. Then it makes
<id>-<version>-store.zip, opens the form in your browser and shows the file in its folder.Sign in, and choose that one file in the form's first field. The form says what it found, such as "From the Workshop: plugin ✓, X4 build ✓, source ✓", and puts the other fields away: they are not needed. Publish a plugin
Press Upload and check, and go on as with any upload: read the checks, complete the entry, send it to the moderators. Publishing with the form
Upload the file as it is: do not unpack it. It holds the plugin's folder as a ZIP (<id>-<version>-upload.zip), each signed build (<id>-<version>-x4.ppn), the source as a ZIP (<id>-<version>-source.zip) and paperpaw-store.ini, which names each part. The site checks each part as if you had uploaded it apart, within the same limits.
Publishing with the form
The form is the main way: you upload, the site checks at once, you complete the entry and send it to the moderators.
Sign in. Enter your e-mail address at
/account/sign-in. The site sends a link that holds for 30 minutes and signs you in once: open it and press Sign in. The first time, give the name the store shows with your plugins (at most 80 characters) and accept the rules for authors (below). There is no password. One cookie keeps you signed in for 30 days. Sign inMake the ZIP of the plugin's folder, with
plugin.iniat its top or inside one folder at its top. Zip the folder itself, as below. Leave out hidden files and what your system adds (.DS_Store,__MACOSX/,Thumbs.db,desktop.ini): the site leaves them out with a warning, and a warning keeps an update from going live by itself.# Linux and macOS: zip the folder itself, without hidden files. zip -r night-owl.zip night-owl -x '*/.*' unzip -l night-owl.zip # Windows 10 and 11, in PowerShell or the command prompt (tar comes with Windows). tar -a -c -f night-owl.zip night-owl tar -t -f night-owl.zipThe list must show
night-owl/plugin.ini(orplugin.ini), and the files ofstore/under the same folder.A code plugin also needs one signed
.ppnfile for each reader it runs on, and its source: a repository and a revision in the section[source]ofstore.ini, or the source as a ZIP. How to make and sign the builds: Code pluginsUpload at
/submit: the ZIP in the first field (package), each build in the field of its reader (build-x4,build-x4-pro,build-x4-classic,build-x3), and the source ZIP, if you have one, insource-zip. Each.ppnis known by its header: one in another reader's field is taken for its own reader. Press Upload and check. If the upload cannot be read, the form says why (checkuploadormanifest). Otherwise the site makes a draft, only you can see it, at/submit/<n>. Publish a pluginRead the checks. Each one names its code and says Passed, Warning or Failed, with a sentence of what is wrong and what is right. What failed comes first. What each code means and how to fix it: The checks
Complete the store entry. What
plugin.iniandstore.ininamed is filled in. Fill in the rest, press Save and check again, and every check runs again. What only a new upload can change (the folder's files, the builds) you change by uploading again.Look at the preview: the plugin on the shelf, and its page as it will look at
/submit/<n>/page. When the site can run PaperPaw's emulator, it also shows the store's page of the plugin on the readers, at/submit/<n>/preview/<device>.png.Send it. The button Send to the moderators shows only when no check fails. Until they decide, you can still change the entry (it becomes a draft again: send it once more) or press Withdraw. Only one version of a plugin waits at a time.
The moderators decide and you get a mail either way. Published: the plugin is in the store, on this site and on the readers. Refused: the mail and the submission's page give their reasons. Upload a changed version to try again, or answer the mail if you think they are wrong.
A submission is a Draft (only you see it), Waiting (for the moderators), Published, Refused, Withdrawn or Taken down. Your account page, /account, lists them all.
The form's fields
The names of the fields, as the form sends them. <lang> is a language code such as de, <file> a screenshot's file name such as home.png.
| Field | What |
|---|---|
The upload, POST /submit | |
package | Required. The plugin's folder as a ZIP, or the Workshop's file (<id>-<version>-store.zip) with all in it; then nothing else. |
build-x4, build-x4-pro, build-x4-classic, build-x3 | A code plugin: the signed .ppn for each reader it runs on. Every build given makes it run on that reader. A build is taken for the reader its header names, whichever field it came in. |
source-zip | Optional. The source as a ZIP, only when store.ini names no repository and revision. |
csrf | Hidden: the session's own token, sent by the page itself. A form from another page or older than the session is refused. |
The entry, POST /submit/<n> | |
name | The name, at most 60 characters. From name in plugin.ini. |
summary | One sentence, at most 160 characters. |
description | More, at most 2000 characters, on the plugin's page. |
group | One of reading, formats, library, sync, home, look, notes, learning, news, tools, games, system. |
tags | Up to five, separated by commas. |
licence | An SPDX identifier, such as MIT. |
source | Where its source lives, an https:// address. |
sdk | The PaperPaw SDK it was made for, such as 0.5. The betas of a version (0.5.0-beta.1) have its SDK: a plugin for them names 0.5 too. |
devices | Data plugins: one box for each device it runs on (android, x4, x4-pro, x4-classic, x3). All of them ticked means every device. A code plugin runs on the readers it has builds for. |
source-repository, source-revision | Code plugins: the repository and the tag or commit the builds were made from. A change fetches the source again. |
source-path, source-bin | Code plugins, optional: the folder of the crate in the repository, and the binary to pack. |
i18n-<lang>-name, i18n-<lang>-summary | The name and summary in a language the entry has. Both fields left empty take the language away. |
i18n-new-lang, i18n-new-name, i18n-new-summary | A new language: its code, the name and the summary. |
screenshot | New screenshots, PNG, JPEG or WebP, up to four in all. |
alt-<file>, remove-<file> | What screenshot <file> shows, at most 200 characters; a ticked remove-<file> takes it away. |
The file store.ini
store/store.ini gives the store what plugin.ini does not. Every key is optional: what is missing, the form asks for. With a complete store.ini, the entry is complete at the upload.
It is PaperPaw's INI: [section] lines and key = value lines. A comment is a line of its own that starts with # or ;. There are no comments after a value: group = look ; my group makes the group look ; my group, and a section line with a comment after it is not read at all. A value is one line; to keep spaces or write line breaks, put it in quotes and write \n. Two complete examples:
# clock/store/store.ini: what the store shows of a code plugin.
# Only the store reads this folder; readers never install store/.
# Every key is optional: the form asks for what is missing.
# Comments go on lines of their own, never after a value.
[store]
# One sentence, at most 160 characters, shown in lists and on the readers.
summary = A large clock on the home screen, with the date below it.
# At most 2000 characters, on the plugin's page. A value is one line:
# put it in quotes and write \n where a new line begins.
description = "The clock shows the time in large figures, and the date below it.\n\nIt adds a widget for the home screen and a setting for 12 or 24 hours."
# One of: reading, formats, library, sync, home, look, notes, learning, news, tools, games, system.
group = home
# At most five, separated by commas: lower-case letters, digits and hyphens.
tags = clock, widget
# An SPDX identifier.
licence = MIT
# Where the source lives: an https address, shown as the plugin's home.
source = https://codeberg.org/your-name/clock
# The PaperPaw SDK it was made for.
sdk = 0.5
[source]
# The exact source the builds were made from: a public repository on
# github.com, codeberg.org or gitlab.com, and a tag or a 40-digit commit.
repository = https://codeberg.org/your-name/clock
revision = v1.0.0
# Only when the plugin's crate is not at the top of the repository:
# path = plugins/clock
# Only when the binary to pack is not named as the crate's package:
# bin = clock
[screenshots]
# A picture in store/, and what it shows, at most 200 characters.
home.png = The home screen with the clock widget, on an Xteink X4.
[i18n.de]
# The name and the summary in another language: both, or neither.
name = Uhr
summary = Eine große Uhr auf der Startseite, mit dem Datum darunter.
# night-owl/store/store.ini: what the store shows of a data plugin.
[store]
summary = Dark headers and roomy rows for reading at night.
description = A theme without code: new defaults for the appearance settings. Anything you choose in Settings still wins.
group = look
tags = theme, dark
licence = CC0-1.0
source = https://github.com/your-name/night-owl
sdk = 0.5
# The devices it runs on: android, x4, x4-pro, x4-classic, x3.
# Leave the key out when it runs on all of them.
devices = android, x4-pro
[screenshots]
home.png = The home screen with dark headers, on a Boox Go 7.
[i18n.de]
name = Nachteule
summary = Dunkle Kopfzeilen und großzügige Zeilen zum Lesen in der Nacht.
| Key | What |
|---|---|
[store] | |
summary | One sentence, at most 160 characters. Without it, the store takes description from plugin.ini when that fits. |
description | At most 2000 characters. Without it, the store takes description from plugin.ini. |
group | Where it belongs: reading, formats, library, sync, home (home screen and menus), look, notes (notes, highlights, journals), learning (vocabulary, languages, flash cards), news (news, feeds, articles, web reading), tools (calculators, timers, utilities), games or system. |
tags | Up to five words to find it by, separated by commas: lower-case letters, digits and hyphens, at most 24 each. |
licence | Its licence as an SPDX identifier: MIT, Apache-2.0, GPL-3.0-or-later, CC0-1.0, OFL-1.1. |
source | Where its source or its home lives: an https:// address. |
sdk | The PaperPaw SDK it was made for, such as 0.5, also for the betas of 0.5. |
devices | Data plugins only: the devices it runs on, separated by commas, of android, x4, x4-pro, x4-classic, x3. Left out: every device. A code plugin runs on the readers it has builds for, whatever this says. |
[source] | |
repository | Code plugins: the public repository, https:// on github.com, codeberg.org or gitlab.com. |
revision | Code plugins: the tag (v1.0.0) or the full commit (40 hexadecimal digits) the builds were made from. Never a branch: it moves. |
path | Optional: the folder of the plugin's crate in the repository, such as plugins/clock, when the crate is not at its top. |
bin | Optional: the binary to pack, when it is not named as the crate's package. Letters, digits, _ and -. |
[screenshots] | |
<file> | A screenshot in store/ by its file name, and what it shows, at most 200 characters, for people who cannot see it. |
[i18n.<lang>] | |
name, summary | The name and the summary in the language <lang> (two or three lower-case letters, such as de), both of them. Not en: English is the name and summary themselves. |
The screenshots are files in store/, .png, .jpg or .webp, named in lower-case letters, digits and hyphens, such as home.png. Translations need no [i18n.<lang>] when the folder's i18n/<lang>.ini has the messages <id>.plugin.name and <id>.plugin.description (a sentence of at most 160 characters): the store takes them, and store.ini wins where it gives both.
The checks
Every version passes these checks, each with a code that stays the same. A check passes, warns or fails, with a sentence. A version can be sent only when no check fails. Warnings are shown to the moderators.
| Code | What it checks | When it fails, and what to do |
|---|---|---|
upload |
The files can be read: the folder's file is a ZIP of at most 20 MB, 40 MB unpacked, no name in it leads outside the folder, each build is at most 4 MB and the source at most 50 MB. It warns when it left files out that are no part of a plugin. | It says "The plugin's file is not a ZIP", "larger than 20 MB", "a name outside the folder" (a name with .. or a backslash, as some Windows tools write them): zip the folder again with the commands above. "Left out what is no part of a plugin: .DS_Store" is a warning: zip without those files. |
manifest |
plugin.ini lies at the top of the ZIP, or in one folder at its top, and its [plugin] names an id, a name and a version. |
It says "The ZIP has no plugin.ini at its top": zip the plugin's folder itself, not the folder around it, and not two folders. "plugin.ini names no id": add the keys it names. |
id |
The id is free, or yours: not a plugin of this site's catalogue, not another author's, not taken down, not offered by another author first. | It says "belongs to another author", "is one of PaperPaw's own plugins", "was taken down and cannot be used again": choose another id in plugin.ini, and in your code, and upload again. |
version |
The version is three numbers like 1.2.0, a pre-release after them or not (1.2.0-beta.1, which comes before 1.2.0), was not published before (taken down versions count), and is not older than the newest. |
It says "was published before; a new version needs a new number", "is older than": raise version in plugin.ini, and in your code's manifest, and upload again. |
metadata |
The store entry, one sentence for each rule it breaks: the id's letters, a name (at most 60) and a summary (one sentence, at most 160), a description of at most 2000, a licence, a group of the list, at most five tags, the languages, each screenshot's name and alt, source an https:// address, sdk like 0.5; for a code plugin also its readers, its key and the source of the version (repository and revision on a forge it knows). |
Complete or correct the field the sentence names in the form, then Save and check again, or in store.ini for the next upload. "version 1.0.0: sdk names no PaperPaw SDK" or "is not like 0.5": fill in sdk, such as 0.5. "source: the revision is a tag or a commit": fill in source-revision. |
package |
The folder readers install: a ZIP of .ini, .css, .ttf, .otf, .ttc, .ppfp, .txt, .md and .png files; names of letters, digits, dots, hyphens and underscores, no part beginning with a dot, at most 100 bytes; at most 64 files, 63 for a code plugin; plugin.ini naming the same id and version, a number in formats; translations that parse. |
It says "only ini, css, ttf, otf, ttc, ppfp, txt, md, png files belong in a data plugin": take that file out (a code.ppn is uploaded as a build, never in the folder). "names are letters, digits, dots, hyphens and underscores": rename the file. "more than 63 files": fewer files. A translation that does not parse names its line: fix it. "Font packs (.ppfp) belong in fonts/": move the font pack into the folder fonts/, not into a folder below it. |
build.x4 build.x4-pro build.x4-classic build.x3 |
Each build, one code for each reader: a PaperPaw plugin file of format 2, for that reader's chip, as long as its header says, its digest whole, and signed by its author. The store reads its fingerprint from the file. | It says "The file is not signed": sign it with paperpaw-sign or in the browser. "Two files are builds for the Xteink X4": you chose the same reader's build twice; choose the other reader's build, or leave its field empty. "is no build for a reader": the file is no .ppn; choose the one paperpaw-sign pack made. "not a PaperPaw plugin file": that is the linked program; make the .ppn with paperpaw-sign pack. "file format 1, from before PaperPaw's plugin interface 2": build it anew with the current PaperPaw release. "Changed or damaged": build and pack it again. "The file of this build is missing": upload it again. |
signature |
All builds are signed by one key (in the catalogue, the key author_key names). |
It says "signed by 2 different keys": sign every build with your one key file. "another key than the entry names": sign with the key in author_key, or correct it. |
key |
The key is the one of the versions before. A first version passes: readers remember its key and expect it for every update. | It says "Earlier versions are signed by the key": sign with the key of your earlier versions. If you lost it, write to the contact address; the store takes no other key without that. |
source |
A code plugin's source: fetched from the repository at the revision, or the ZIP uploaded; at most 50 MB; a ZIP with Cargo.toml at its top or at path; and a Cargo.lock. It warns without Cargo.lock. |
It says "name a repository and a revision": fill in [source] or the form's fields. "could not be fetched from": push the tag, make the repository public, check the address. "has no Cargo.toml at its top": set path to the crate's folder. "no Cargo.lock": commit it and tag again. |
how |
A code plugin says how it works: what it does, where it is found and how it is used, in at least 120 characters, in how/en.txt in the folder (a file per other language, how/de.txt, as its manifest names them) or in the description. The store shows it on the plugin's page as How it works, and readers in their store. Only a warning. |
It says "nothing of how it works" or "says little": put the plugin's How it works, the files its manifest names (Manifest::how), into the folder as how/<lang>.txt, or write a longer description in the form, then Save and check again. |
screenshots |
At most four pictures, each a PNG, JPEG or WebP of at most 500 KB. It warns when there are none. | It says "at most 500 KB": make it smaller, as a PNG with fewer colours or a WebP. "is a PNG, JPEG or WebP picture": convert it. "No screenshots" is a warning: add one of the plugin at work. |
What a submission's page lists is what the site found: the sentences in its own words, which this table quotes in part. Once you send it, the moderators also see what PaperPaw's own checker, paperpaw-dev check, finds: the plugin drawn on the emulated X4, X4 Pro and Boox, its texts, reach and budgets. It decides nothing; a moderator may pass it on to you. A code plugin is drawn only if its source has the harness of PaperPaw's template (examples/paperpaw-dev.rs, see "Checking your plugin" in PaperPaw's docs/plugins.md); run the same check yourself before you send.
Code plugins
Start from PaperPaw's sample, a code plugin in a workspace of its own as a plugin's own repository is: its Cargo.toml, .cargo/config.toml with paperpaw-plugin.x beside it (the linker script of the PaperPaw release, which no build script brings), rust-toolchain.toml, plugin/plugin.ini and a CI job, .github/workflows/build.yml. samples/hello-native
Build for a plugin interface. A build runs on every PaperPaw of the plugin interface it was built for, a beta or a release alike, and only where the same compiler built the firmware. Its fingerprint, 16 hexadecimal digits in the file's header, holds the interface. About on the reader shows its fingerprint and the compiler; each PaperPaw release lists them in its files
paperpaw-<reader>-<version>-abi.txt, and the sample's CI job names the compilers (RUST_X4,RUST_X4_PRO). Name the SDK of that release inCargo.toml:paperpaw-sdk = "0.5.0-beta.1"for the beta (Cargo takes a pre-release only when it is named),"0.5"for the release. Build again when a release raises the plugin interface; its notes say so, and so does the plugin manager on the reader.Build one file for each reader, the X4 and the X4 Pro. PaperPaw does not run on the X4 Classic yet, so its field in the form is for later: it has the X4 Pro's chip but a firmware of its own, so give it a build only with the fingerprint its About shows.
cargo x4 # the X4: riscv32imc-unknown-none-elf cargo +esp x4pro # the X4 Pro: xtensa-esp32s3-none-elf, espup's toolchainMake your key once, with
paperpaw-signof the release:# paperpaw-sign of the release you build for (here the beta 0.5.0-beta.1), # which is also in the emulator's ZIP of the release: cargo install --locked --git https://github.com/hthoene/paperpaw --tag v0.5.0-beta.1 paperpaw-sign # Once: make your key. Keep the file private, with a copy somewhere safe. paperpaw-sign key paperpaw-author.keySign in your CI: the sample's job builds the plugin on every tag
v*, packs and signs a file for the X4 and the X4 Pro, and attaches them to the tag's release with their SHA-256. Put the contents of your key file in the repository's secretPAPERPAW_PLUGIN_KEYand setPLUGINto your binary's name.Or pack and sign on your own computer:
# Pack and sign in one step, for each reader: paperpaw-sign pack target/riscv32imc-unknown-none-elf/release/clock clock-1.0.0-x4.ppn --key paperpaw-author.key paperpaw-sign pack target/xtensa-esp32s3-none-elf/release/clock clock-1.0.0-x4-pro.ppn --key paperpaw-author.key # Or sign a file packed without --key: paperpaw-sign sign-plugin clock-1.0.0-x4.ppn --key paperpaw-author.key # What a file holds: its chip, its fingerprint and its signer. paperpaw-sign plugin clock-1.0.0-x4.ppnOr sign in the browser: pack with
paperpaw-sign packwithout--key, open the signing page, choose your key file (or make one there, saved aspaperpaw-author.key) and your.ppnfiles, sign them and save each. Your key and your files never leave your computer. It needs a browser that signs with Ed25519: Firefox 129, Chrome and Edge 137, Safari 17 or later. Sign in the browserName the source. Tag the commit you built from and push the tag. In
store.ini,[source]names the public repository (github.com,codeberg.orgorgitlab.com) and the tag or commit asrevision; the site fetches the forge's ZIP of that revision itself. When the crate is not at the repository's top,pathnames its folder; when the binary is not named as the crate's package,binnames it (the sample's package ispaperpaw-sample-hello-nativeand its binaryhello, so it needsbin = hello). A source elsewhere goes up as a ZIP insource-zip, withCargo.tomlat its top or in one folder at its top.
The key file is one line of 64 hexadecimal digits, your secret. Its public key, also 64 hexadecimal digits, is what the store shows as yours and what readers remember. The same file signs with paperpaw-sign and in the browser. Keep it private and keep a copy apart: whoever has it can sign in your name, and without it no update of yours installs without a warning. Never commit it, never upload it.
Paths end up in a build, so build with the sample's build.sh: it writes them as the rebuild (below) does, the top of your source as /plugin, cargo's home as /cargo, and for the X4 Pro the toolchain as /rust (--remap-path-prefix). Built so on Linux or macOS, in any folder, your build matches its rebuild; built without it, it may differ for its paths alone. On Windows paths keep their \, so build in WSL or with the sample's CI job.
Commit Cargo.lock and build with --locked: without it, the source cannot be rebuilt to compare, and the check source warns.
Updates
Give the new version a higher number in
plugin.ini(and in your code's manifest), newer than the last. A number once published, also one taken down, is never used again.Upload the new folder at
/submitwith the account that published the plugin, with new builds and the new source for a code plugin. What the upload leaves out is taken from your last published version: summary, description, group, licence, source, sdk, tags, translations, screenshots, andpathandbin.Sign it with the same key. Readers remember a plugin's key at the first install and warn when an update is signed by another, so the form takes no other key (check
key). If you lost your key, write to the contact address: a moderator can change it in the open, recorded in the version log, never silently. Readers that have the plugin then warn about the update. The contact addressEvery update waits for the moderators, as a new plugin does: nothing goes live before a person has looked at it. You get a mail when they decide.
In the catalogue, an update is a new [[versions]] block in the plugin's TOML, beside the ones before, which stay.
A new PaperPaw release
A code plugin runs on every PaperPaw of the plugin interface it was built for, so it needs a new build only when a release raises that interface. Before a release, and before each beta, the store builds your plugin's latest version from its source against the release candidate, checks it with that release's paperpaw-dev and mails you whether it builds and passes, with the log and the build; if it did not pass, a reminder follows a week later. Where a release raises the interface, readers who use your plugin wait for its new build before they update, and see that they wait.
Try it yourself: take the release candidate's SDK (this updates
Cargo.locktoo) and build. What the log of the store's build shows, you see here too.# Against the release candidate the mail names (here v0.6.0-rc.1): cargo add paperpaw-sdk --git https://github.com/hthoene/paperpaw --tag v0.6.0-rc.1 ./build.sh # Then commit Cargo.toml and Cargo.lock together.Once it builds, change three lines of the sample's CI job and two of your source, then tag: the job builds, packs and signs both files with your key and attaches them to the release, with the catalogue snippet.
# .github/workflows/build.yml, the sample's job: the new release and its # compilers, as its versions.env and shells/xteink/rust-toolchain.toml name them. env: PAPERPAW: "0.6.0" RUST_X4: "<the release's X4 compiler>" RUST_X4_PRO: "<the release's X4 Pro compiler>" # Cargo.toml: the release's SDK. plugin/plugin.ini: a higher version. Then: git commit -am "PaperPaw 0.6" && git tag v1.1.0 && git push --tagsWithout the CI job, one command builds and signs, or signs a file packed without a key. The store's build is not signed: a reader runs only what its author signed, and the store holds no key of yours. Sign it only when your committed source is what it was built from, the SDK line in
Cargo.tomlandCargo.lockincluded.# Build, pack and sign both files in one step: ./build.sh --key paperpaw-author.key # Or sign files packed without a key, such as the store's build: paperpaw-sign sign-plugin clock-1.1.0-x4.ppn --key paperpaw-author.keySend the version in at
/submitas any update, signed with the same key. If the release keeps its candidate's plugin interface and compilers, the fingerprint is the same and you can send it in before the release; otherwise send it in once the release is out.
What the store shows
The site keeps a copy of the source of every version of a code plugin and shows it to everyone, file by file, at /plugins/<id>/<version>/source, with a link to the revision in your repository. Whoever installs a plugin can read what they install.
The files of a data plugin are shown the same way, at /plugins/<id>/<version>/files.
Each build of a code plugin is rebuilt from the source kept here, in an isolated container without network, with the toolchain the build's fingerprint names, and compared with your file without its signature, byte for byte.
The plugin's page then shows "Verified: matches its source", or "Not rebuilt" while it waits, when no toolchain is known for its fingerprint, or when it could not be rebuilt, or "Rebuilt, but does not match its source". The rebuild never holds a plugin back, and readers always get the file you signed: it is a check, not a build service. Builds for the X4 Classic are not rebuilt yet.
Anyone can repeat the check. It fetches the build and the kept source from the site, checks both against the SHA-256 the site lists, rebuilds and compares. Only the newest version of a plugin is listed, and so checked.
# From a copy of the paperpaw.org repository, with Docker:
cargo run -- verify clock 1.0.0 --device x4
paperpaw-website verify <id> <version> [--device x4|x4-pro] [--site https://paperpaw.org]
# Or with bash, curl, sha256sum and Docker alone:
rebuild/verify.sh <id> <version> [x4|x4-pro] [site]
It prints MATCHES or DIFFERS with both hashes, and ends with 0 when they match, 1 when they differ and 2 when it could not tell.
The public version log lists every version ever published here and every one taken down, by either way, with the SHA-256 of its package and of each build, its author's key and its source's revision. Nothing in it is ever changed or removed, and each entry names the hash of the one before. As JSON: /api/v1/log.json?after=<n>. The version log
The rules for authors
You accept these rules when you make your account. They hold for every plugin and every version.
Publish only what you have the right to publish: your own work, or work whose licence lets you share it, with its authors named as their licence asks. That includes fonts, pictures and translations.
Give it a licence, as an SPDX identifier, that lets everyone install and use it, and include the licences of what it carries, such as a font's licence as a
.txt.Be honest in its name, summary, description and screenshots: say what it does and what it does not, show the plugin itself at work, and use no names or logos of others that suggest it is theirs.
No malware, and nothing that harms readers or their devices: no hidden functions, nothing that damages, locks or slows a device on purpose, nothing that takes books, files or accounts.
No hidden collection of data, and no hidden sending of it.
Say in the description what the plugin sends over the network, to whom and why. It sends nothing else.
Personal data: collect only what the plugin needs to work, say so in the description, and follow the law that applies to you, in the EU the GDPR. Put no one's personal data into the plugin, its screenshots or its entry.
The builds come from the source you name: every build is made from that revision, unchanged, so that anyone can read and rebuild what they install.
It is free: anyone may install and use it without paying.
When a plugin is reported
Anyone can report a plugin from its page, at /plugins/<id>/report, without an account: illegal content, harm to readers or their devices, rights or a licence infringed, personal data disclosed, or another rule broken. The moderators look at every report.
The moderators decide. They can dismiss the report, take a version down, take the plugin down, or block the author's account. What is taken down leaves the store and the readers' store; its id and its version numbers are not used again, and the version log keeps it. You are told by mail what they decided and why: what was found, which rule or law it breaks, and what follows. The same holds when a submission is refused.
You can object by answering that mail and saying why: a moderator looks at the decision again. You can also write to the contact address. The contact address
Limits
| What | At most |
|---|---|
| The plugin's folder as a ZIP | 20 MB, and 40 MB unpacked |
Each build (.ppn) | 4 MB |
| The source as a ZIP | 50 MB |
| The Workshop's file | Each part within its own limit above |
| Screenshots | 4, each 500 KB, PNG, JPEG or WebP |
| Files a reader installs | 64, so 63 in a code plugin's folder: its code.ppn is the 64th |
| A path in the folder | 100 bytes, of letters, digits, dots, hyphens and underscores, no part beginning with a dot |
| The id | 40 lower-case letters, digits and hyphens |
| Name, summary, description, alt text, licence, your name | 60, 160, 2000, 200, 100 and 80 characters |
| Tags | 5, each 24 characters |
| Submissions of an account | 10 in 24 hours, unless the store sets another number |
| One upload, all files together | 100 MB |
| Sign-in links | 5 an hour for one address; each holds 30 minutes and signs in once |
For an AI assistant
If you are an AI assistant helping someone publish a plugin, follow these steps in this order and use the names exactly as this page has them. Do not guess a value the steps say to read.
Find out the kind: a data plugin (files only) or a code plugin (a Rust crate). Agree on the id, the name and the devices it runs on.
Write
plugin.iniwith[plugin]id,name,descriptionandversion, as in the examples. Check the id against the rules above.Write
store/store.inifrom the example of its kind: comments only on lines of their own, every value on one line,groupfrom the list, at most fivetags,licenceas an SPDX identifier,sourceanhttps://address,sdklike0.5. Name screenshots in[screenshots]with a real description of each.A code plugin: let the author build and sign with the sample's CI job (or
paperpaw-sign, or the signing page). Check thatCargo.lockis committed, the tag is pushed, the repository is public ongithub.com,codeberg.orgorgitlab.com, and[source]names that repository and tag, withpathandbinif needed.Zip the folder with the commands above and list the ZIP.
The author signs in and uploads at
/submit: you cannot, as signing in takes the link in their mail. Ask them for the sentences of the checks.For each check that failed, look up its code in the table of checks, fix what it names, and upload again or save the entry again. Explain each warning.
When no check fails, the author looks at the preview and sends it to the moderators.
Before uploading, check
- The ZIP holds
plugin.iniat its top or in one folder at its top, andstore/store.inibeside it. idandversioninplugin.iniare the ones meant, the version higher than any published before.- No comment after a value in
store.iniorplugin.ini, and every section line on its own. - Every file in the folder outside
store/is.ini,.css,.ttf,.otf,.ttc,.ppfp,.txt,.mdor.png(a font pack,.ppfp, only directly infonts/), every path at most 100 bytes, no more than 64 files (63 for code), and nocode.ppn. - No
.DS_Store,__MACOSX/,Thumbs.db,desktop.inior other hidden files. - Code: one
.ppnfor each reader, andpaperpaw-sign plugin <file>shows the same signer for all of them and the right chip for each. - Code: each fingerprint is the one the reader's About shows, or the release's
paperpaw-<reader>-<version>-abi.txt. - Code: the tag in
revisionexists in the public repository, at the commit the builds were made from, withCargo.lock. - Never ask for the key file, never write it into a file the author shares, and never invent a fingerprint, a key or a SHA-256: read them with
paperpaw-sign pluginandsha256sum.