Self-hosted service
cmintey/wishlist avatar
cmintey/wishlist

cmintey/wishlist: a self-hosted gift list where claims stay hidden

Wishlist is a self-hosted wishlist application that you can share with your friends and family. You no longer have to wonder what to get your family for the holidays, simply check their wishlist and claim any available item!

641 stars54 forksTypeScriptMIT

At a glance

What is it?
Wishlist is a TypeScript app you run yourself so family and friends can claim items without the recipient seeing who took what. It is a good fit for a household or a small group with a Docker host; it is a poor fit if you need a managed service or a multi-tenant setup.
Who is it for?
Adopt Wishlist if you already run Docker Compose and want a private list where claims stay invisible to the recipient, and if you are willing to keep the sqlite volume and the uploads volume backed up. Do not adopt it if you need a hosted service, a subpath deployment, or a way to un-claim a registry item, because the documentation states there is no such flow.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem Wishlist solves is not the list, it is the claim

Most gift lists fail at the same point. Someone shares a list, everyone reads it, and then two people buy the same thing because nobody can see what has already been taken. Wishlist puts the claim in the application rather than in a group chat. The README states the goal plainly: you check their wishlist and claim any available item. The claim is the unit of coordination, not the item.

The audience is narrow and specific. It is a person who already has a Docker host, a domain or a LAN address, and a group of people who will tolerate creating an account. The README says the interface is simple enough that "even the grandparents can get involved", which is a claim about the UI rather than about the onboarding. Onboarding still means an account, unless you use Registry Mode, where the README says users accessing the public link do not need to sign in or create an account. That distinction matters more than any feature list. If your recipients will not make accounts, Registry Mode is the only path that works, and it changes the shape of the group.

Groups, managers and the permission model you inherit

A Wishlist instance is organised into groups. The README gives the example of one group for friends and one for family, and states that wishes on these lists are completely separate. You switch groups from the menu behind your profile picture. Separation here is a data boundary inside one instance, not a separate deployment.

Permissions are flat and few. Anyone can create a group, and the creator becomes a manager of that group. A manager can invite users, add or remove existing users from the group they manage, and delete the group. An Admin has the same permissions as a group manager. There is no described role for a read-only member or a co-manager, so if a group manager leaves, the group's fate depends on whoever holds the Admin account. That is a real operational constraint for a family instance that outlives the person who set it up.

Public signup is on by default, which the README notes means anyone with the URL can create an account. You can turn it off and move to invite only. Without SMTP, the README says an invite link is generated for you to copy and send manually, so invite-only mode does not require a mail server.

Suggestions, and the Surprise Me mode that hides items from their owner

Suggestions are enabled by default, which means other people can add items to your wishlist. The README describes three methods. Under Approval Required, the suggested item waits for the suggestee to approve it before it appears. Under Auto Approval, it is added immediately. Under Surprise Me, the item is added automatically and is visible to everyone except the suggestee, who cannot see it and therefore cannot edit or delete it.

Surprise Me is the most interesting design decision in the project and also the one with the sharpest edge. It creates an item on a user's list that the user cannot see or remove. If a suggestion is wrong, offensive, or simply a duplicate, the person who owns the list has no in-app route to fix it. The README does not describe an admin override for this case. If you enable Surprise Me for a group, you are accepting that the list owner is not the final authority over their own list.

In the other two modes the suggestee can edit and delete an approved item at any time, which is the behaviour most people would expect.

Registry Mode trades accounts for a one-way claim

Registry Mode converts a group into a single list owned by one user. The README states that in this mode only a single user can be part of the group and there is only one list. The owner adds items as usual and then gets a public link. People who open that link see the items and can claim them without signing in. To claim, they enter an identifier such as an email address and can optionally add a name.

The limitation is stated directly in the README: currently there is no way to un-claim items that are claimed in this manner. There is no account behind the claim, so there is nothing to authenticate a reversal against. For a wedding or baby registry where claims are final, that is fine. For a family list where someone clicks by mistake, the only remedy is outside the application. You activate this mode from the admin panel of your group by changing the mode from Wishlist to Registry, which suggests the switch is not a one-way door at the data level, though the README does not describe what happens to existing claims if you switch back.

Running it: one compose file, four environment variables

The README gives a Docker Compose file with the image ghcr.io/cmintey/wishlist:latest, port 3280 mapped to 3280, and two volumes: ./uploads for user image uploads and ./data for the sqlite database. Run docker compose up -d and connect at http://<host>:3280.

The environment variables are few enough to list in full. ORIGIN is the URL your users connect to, and the README warns that if this value is an IP address it must include the exposed port. It also carries a note: set ORIGIN to the URL you will be connecting to, otherwise you will experience issues. TOKEN_TIME is the number of hours that signup and password reset tokens remain valid. DEFAULT_CURRENCY is an ISO code used globally, and currency can still be changed per item. MAX_IMAGE_SIZE caps uploads in bytes and defaults to 5000000, or 5MB.

Two deployment constraints come from the README rather than from guesswork. Wishlist does not support running on a different subpath, so https://domain.com/wishlist is not a supported layout. And there is a known issue behind Nginx or Synology NAS proxies, where the README recommends setting proxy_buffer_size 128k, proxy_buffers 4 256k, and proxy_busy_buffers_size 256k. A community-contributed Helm chart exists at mddeff/wishlist-charts, which the README links but does not maintain.

There is no ORIGIN-less mode and no documented way to run two instances against one database, so the sqlite file in ./data is the whole state of the instance. Treat it that way.

Authentication and mail are optional, and the app is shaped around that

SMTP is not required. The README says SMTP enables inviting users by email and the forgot password flow, and that without it you can still generate invite links and password reset links manually. This is a deliberate choice that keeps the minimum deployment to one container and two volumes. The cost is that password resets become a manual operation for whoever administers the instance.

External authentication arrives through OpenID Connect, documented as available since v0.42.0. You configure it in the Wishlist Administration Settings page with an Issuer URL, a Client ID, and a Client Secret, with all other configuration optional. The README names Authelia, Authentik, Keycloak, and Google as examples, and states that role-based access should be handled in your identity provider. The redirect URL to register with the provider is https://<my_wishlist_domain>/login.

That last detail is worth pausing on. The redirect path is /login, not a callback path under an /auth prefix, and it reinforces the subpath restriction: the app assumes it owns the root of the hostname it is served from. If you are already running several services on one domain under path prefixes, Wishlist needs its own hostname or its own port.

Where Wishlist is the wrong tool

Wishlist is a single-instance, sqlite-backed application. That is a reasonable design for a family, and a bad one for anything with tenants. If you want to host lists for unrelated groups of strangers, the group model does not give you isolation beyond what groups provide, and public signup being on by default means you would be running an open registration endpoint until you turn it off.

The claim model also has a gap. In Wishlist groups, claims are visible to the group, which is the point. In Registry Mode, claims are anonymous and irreversible. There is no mode described in the README where claims are private to the claimer but reversible by them, and no notification described when an item you claimed is edited or deleted by its owner. If your process depends on knowing that the thing you claimed still exists in that form, the README does not promise it.

Finally, the absence of a subpath deployment and the Nginx buffer issue mean the deployment surface is narrower than a generic web app. If your reverse proxy setup is fixed and you cannot add a hostname or change buffer settings, this is a poor match regardless of the feature list.

What to compare it against, and what to check before you commit

The obvious alternative is a plain shared document or spreadsheet with a status column. That approach has no accounts, no container, and no database, and it fails exactly where Wishlist succeeds: anyone can edit anyone else's row, and the recipient can read the claim column. If the group is small and trusting, the spreadsheet wins on operational cost. Wishlist wins when the recipient must not see the claims.

A second alternative is a general purpose list or note app with sharing. Those give you a list and a checkbox but no concept of a claim as a distinct act with an identity attached, and no group manager role, so coordination happens in conversation. The difference is not the storage, it is that Wishlist models the claim as first-class data.

Before you adopt it, verify in your own environment: that ORIGIN matches the exact scheme, host and port users will type, since the README ties problems to getting this wrong; that ./data and ./uploads are on persistent storage and that a container recreate leaves the sqlite file intact; that your proxy is not Nginx or Synology without the three buffer directives; and that you have decided between Wishlist mode and Registry mode, because the claim semantics differ and the README states Registry claims cannot be undone in the app.

On maintenance: the project is MIT licensed, which permits commercial use and modification, but the README does not describe a support commitment, and licence terms are not a substitute for your own review. The release history shows frequent patch versions, including two on the same day, so pinning an image tag rather than using latest is the safer default for an instance your family depends on in December.

Editorial conclusion

Adopt Wishlist if you already run Docker Compose and want a private list where claims stay invisible to the recipient, and if you are willing to keep the sqlite volume and the uploads volume backed up. Do not adopt it if you need a hosted service, a subpath deployment, or a way to un-claim a registry item, because the documentation states there is no such flow. Before you commit, verify three things on your own host: that ORIGIN matches the exact URL and port users will type, that the sqlite file in ./data survives a container recreate, and that your reverse proxy has the Nginx buffer settings applied if you are behind Nginx or a Synology NAS.

Official sources

  1. cmintey/wishlist on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Community notes

Community notes