GrainFed

What's new

The work since the first numbered release, by theme, newest concerns first. Everything here is in the source with the tests that prove it; this page is the plain-language version.

Where to get it. Everything on this page is in release 0.2.0, published with checksums, and in the source. The older 0.1.0 binaries predate most of it.

October 2026

Pixelfed 0.15.0and the new mobile app

  • Pixelfed 0.15.0 compatibility. The route list was re-read from the 0.15.0 release itself: GrainFed serves 947 of the 950 routes Pixelfed can serve, including all 301 that are a hard external contract (the two APIs and federation). The three it does not serve are two links into Pixelfed's queue dashboard, which GrainFed does not have, and one route Pixelfed itself only turns on by configuration. The release's behaviour changes are matched, and the version GrainFed reports is now 0.15.0.
  • The Pixelfed 2.0 app. The app's source on the public repository does not include the redesign, so what it asks of a server was read from the released app itself. Every server call found in it is served. Two are new: the timeline settings (whether boosts appear in your home feed, and whether only photographs do) and removing your own account, which needs your password and, as on the web, flags the account for deletion with a month's grace and signs every app out.
  • Boosts in the home feed. That setting now does something: with it on, people you follow boost posts into your feed, with the original author shown for apps that expect it.
  • Messages. The 2.0 app talks to conversations only, so an instance still on the older message tables could not send or read them. The tables Pixelfed 0.14 uses can now be added with one script (see Migrate), and a command converts the older messages without removing any. Until the tables exist, the app is told that group conversations are not offered rather than being sent to a screen that cannot work.

Spamsign-up protection

One mailbox can no longer make a crowd of accounts. An address with a plus sign ([email protected]) delivers to [email protected], which is the cheapest way round "one account per address", and spam sign-ups use it. Three rules now guard every way of creating an account, and of changing your email address:

  • Plus-addressed email is refused, with a message that says why.
  • A new address that is only another spelling of one already registered at Gmail (dots, +tags, googlemail.com) is treated as already registered.
  • Addresses at domains on a list you edit are refused, so nothing is even emailed to them. A short starter list of well-known throwaway-mail services is in force until you save your own.

All three are on by default and each has one button to turn it off: System settings → Sign-up protection. Existing accounts are not touched.

Fixedfound by running it

  • Public profile pages, embeds and Atom feeds were empty. An account with 316 public posts served a page with none and a feed with no entries, and had for weeks. They list the posts again.
  • Posts from Bluesky-bridge accounts were being lost. 142 bridged accounts could not be read because their profile document describes a featured collection in a form we did not accept, so every post from them was dropped. The same family of defect stopped PeerTube videos (a duration written as PT15M1S) and media with a size written as text. All are read now.
  • Boosts made on the website were never sent to the Fediverse. Two internal calls had their arguments in different orders, so the booster's own followers elsewhere never saw the boost. Likes and boosts of other servers' posts also now name the post the way those servers expect, and a boost carries its real date.
  • A retried post is no longer posted twice. An app whose connection dropped sent the post again and got a duplicate. The retry now receives the original.
  • Followers can see follower-only posts on a profile; a moved account no longer lists its old posts or followers; and looking an account up as name@this-server, which apps do, finds it.
  • Replies. The author of a followers-only post sees replies to it from people they do not follow back, and a page of replies is no longer short because some were hidden.
  • Deleting a post now updates every count and cache it touched, as removing it from the website always did.
  • Hashtag feeds page the way Pixelfed's now do, with a limit on how deep a feed can be walked, which stops a scraper reading a tag's whole history. A tag can be followed up to 100 at a time.
  • An update to someone's profile can no longer wipe the key their server signs with, and a peer's bio and name are cleaned before they are stored.
  • The content-warning setting on an account is honoured when a post is edited and on its photographs.
  • Colour profiles are kept when a JPEG, PNG or WebP photograph is resized, so wide-gamut pictures no longer come out dull. (HEIC photographs still lose theirs.)
  • The language you choose is stored in the form Pixelfed 0.15.0 expects.
  • Image licences travel with photographs between servers (the new FEP-6757 standard), and the licence picker lists the licences that actually exist.

Photo albumson a computer

An album now has previous and next arrows over the picture, and the left and right arrow keys move the album that is on screen, as long as you are not typing in a field. On a phone you still swipe. The pixel size that used to be printed under photographs is gone.

Fasterperformance

The instance's counts are cached, for as long as you say. The number of people and posts on the landing and about pages, in /api/v1/instance and in NodeInfo used to be counted from the database on every request. On a small server that was the slowest thing it did. They are now kept, and a figure that has expired is still shown while a fresh one is counted in the background, so no visitor waits for it.

The lifetime is a setting: System settings → Performance. Two hours is recommended and is the default. Pixelfed itself keeps these for twelve hours, and the page says so. Zero turns the cache off; the most is a day.

Saferfederation

  • A stricter check on account-deletion messages. When a server announces that one of its accounts is gone, the check that the message really came from that account is now tighter. Found by reading the code, not by an attack, and fixed with a test. It is in release 0.2.0, and it is the reason not to stay on 0.1.0.
  • Dead accounts stop being retried. A suspended or deleted account's server keeps delivering its "I was deleted" message for ever, and each one was answered with an error that means try again later. The production log held about 9,300 of them. A deletion from an account nobody here has heard of is now answered once with "received" and dropped: there is nothing to remove.

Admin panelfixes

  • A decided application shows its outcome, not the tools for deciding it. Once a curated-registration application is approved or rejected, the page says so, links to the person's account, and no longer offers approve, reject or resend as if it were pending.
  • A client could not register on the API. POST /api/v1/apps, which every Mastodon-style app calls first, failed on any instance whose .env did not set BCRYPT_COST — the same defect that had broken registration and password resets, found a second time in the production log. It is fixed where the cost is used, so no caller can miss it.
  • The new logo is in the admin panel, the web interface and the sign-in page, and the accent colour is its orange.

September 2026

Pixelfed 0.14.4compatibility

  • Pinned to 0.14.4. Every route 0.14 added is served, the configuration tables follow its renamed environment variables (the old spellings keep working, because a real .env uses them), and the version a peer or a crawler sees is the Pixelfed release GrainFed claims. A database still at the 0.12.9 baseline is reported as supported, not behind.
  • Direct messages read and write the new dm_* tables when they exist, keep the older path when they do not, and never create the tables themselves. Federation, notifications, reports and account purge all follow.
  • NodeInfo reports the Pixelfed release rather than the Mastodon API version it used to — a mistake found only by looking at the live instance after deploying.

Federationstandards

  • Follower synchronisation (FEP-8fcf), so a follower list stays consistent between servers instead of drifting.
  • Quote posts (FEP-044f), as the server that authorises them, with the interaction-policy API and a privacy page. Consent lives in GrainFed's own tables.
  • Featured collections (FEP-7aa9) and block synchronisation (FEP-070c), each in both directions where there are two.
  • Direct messages carry context and conversation on the way out.

Sign-up and sign-insecurity

  • The 0.14.4 login flow. Nobody has a session until the password, the second factor and the email check have all passed, and a deep link survives the steps.
  • Captcha with three providers — hCaptcha, Cloudflare Turnstile and Cap — switchable per surface (login, register, password reset, curated application, in-app sign-up), with a choice of whether it fails open.
  • A honeypot on registration, a ban on sign-up and sign-in from cloud-provider addresses, and a user limit.
  • A minimum age (16 by default, an instance setting): the register form asks, and the curated application has the rules page and the age gate. The date of birth is read in the browser and never sent.
  • Curated applications email the administrators — all of them or the ones you name — when an applicant confirms their address, and a failure to send can never spoil that confirmation.
  • Email verification is one thing everywhere: the login flow, the resend on the settings page, registration, and admin invites.
  • In-app sign-up follows 0.14.4: an app that registered a client gets a token and a working refresh token against it, the redirect_uri is checked against your allowlist of schemes, and errors name their code.

Stabilityfound by running it

  • Two things at once. MariaDB 11.6 and later refuses a transaction that writes a row another has changed since it began, and a pair of them can deadlock. Twelve people blocking one account at the same moment failed most of the time, and the production log held two such failures on the queue. Writes now run again a bounded number of times, as Laravel's own transactions do.
  • An unset BCRYPT_COST made every registration and password reset fail, and a waiving admin invite did nothing on an instance that enforces email verification. Both fixed after being found in use.
  • Older in-app sign-up created accounts verified where verification is not enforced, and answered a bad link in the wrong field for the app to read. Fixed.

Before that0.1.0

The first numbered release: every servable route, the admin panel and settings tree, the web interface, the job queue and scheduler in the binary, and the adopt, doctor, probe, parity and optimize commands. The migration and install pages describe them.

Migrate an instance Read the source