Boutique gives SwiftUI apps a persisted Store without a database model
✨ A magical persistence library (and so much more) for state-driven iOS and Mac apps ✨
At a glance
- What is it?
- Boutique is an MIT licensed Swift persistence library that turns a model into a Store backed by an in-memory cache and disk storage, with @StoredValue and @SecurelyStoredValue property wrappers for single values. It suits SwiftUI, UIKit and AppKit developers, and it is built on top of Bodega.
- Who is it for?
- Adopt Boutique if you build Apple platform apps and want persisted, realtime-updating model collections without designing a database schema or writing a repository layer. Do not adopt it if you are committed to Core Data, since the quoted reaction it exists to replace is precisely that.
- 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 last received commits 102 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One concept, and the reaction that explains the design
Boutique is a simple but powerful persistence library, a small set of property wrappers and types that enable building incredibly simple state-driven apps for SwiftUI, UIKit and AppKit. With its dual-layered memory and disk caching architecture, it provides a way to build apps that update in real time with full offline storage in only a few lines of code.
The design goal is stated as a single concept. When you save data to the Store, your data is persisted automatically for you and exposed as a regular Swift array. The @StoredValue and @SecurelyStoredValue property wrappers work the same way, but instead of an array they work with singular Swift values. You'll never have to think about databases, everything in your app is a regular Swift array or value using your app's models.
The reason the project exists is quoted at the top of the README, from Josh Holtz: I ripped out Core Data, this is the way it should work.
That quote is the clearest statement of the proposition, and it is worth taking seriously rather than reading as a marketing line. The implied claim is that for a large class of apps a heavyweight persistence setup is not needed.
Two more testimonials in the same vein describe the appeal. Tyler Hillsman calls Boutique ridiculously easy to implement and says it makes persistence a breeze and has become the first addition to every project he starts. Romain Pouclet says it has become invaluable, that he uses it in every side project, and that the cost of getting started is practically zero.
The claim about ease of getting started is the one to test, because the library's own docs are the evidence for it.
The Store surface is three methods, and uniqueness is not your problem
The whole API for full offline support and realtime model updates is three methods: insert, remove and removeAll. The README's example shows the whole thing.
// Create a Store ¹
let store = Store<Animal>(
storage: SQLiteStorageEngine.default(appendingPath: "Animals"),
cacheIdentifier: \.id
)
// Insert an item into the Store ²
let redPanda = Animal(id: "red_panda")
try await store.insert(redPanda)The store is generic over your model, and it is constructed with a storage engine and a cache identifier, which is the key path of the model used to identify items. The example uses SQLiteStorageEngine.default, passing in a path for the Store's storage.
Uniqueness is handled by the Store rather than by you, and the example demonstrates it rather than asserting it:
// You also don't have to worry about maintaining uniqueness, the Store handles uniqueness for you
let secondDog = Animal(id: "dog")
try await store.insert(secondDog)
print(store.items) // Prints [dog, cat]Inserting a second dog does not produce a second dog. That is the kind of detail that saves an entire category of bug in a cached-data layer, and it comes from the cacheIdentifier.
The store also supports chaining, which is how a multi-step mutation becomes one operation:
// You can even chain commands together
try await store
.insert(dog)
.insert(cat)
.run()And the README calls out the specific pattern that chaining is good for, clearing stale cached data in one go:
try await store
.removeAll()
.insert(redPanda)
.run()Reading is direct, with store.items giving a Swift array, and the array is the live one rather than a copy you have to refresh.
Many Stores are allowed, and the choice is deliberately yours
The footnotes to the example carry two design notes, and the first is about a decision most libraries would make for you.
The first footnote says you can have as many or as few Stores as you like, and that it may be a good strategy to have one Store for all the notes you download in your app, while you may also want one Store per model type you would like to cache. You can even create separate stores for tests. Boutique is not prescriptive and the choice for how you would like to model your data is yours.
That matters because the caching story depends on it. A single Store for everything gives you one cache and one eviction policy for the whole app. A Store per model type gives you finer control over what gets invalidated when. A Store per feature gives you a lifetime that matches a screen. None of these is obviously right, so the library declines to decide.
The note says this is a concept from Bodega, and it points at Bodega's StorageEngine documentation for the details. Bodega is the underlying project Boutique is built on top of, and the split is worth understanding. Bodega provides the storage engine abstraction and the caching machinery, and Boutique is the Swift-facing layer on top of it: the Store, the property wrappers and the ergonomics. If you are evaluating Boutique for a project that needs a custom persistence backend, Bodega is where you would look, because the storage engine is the seam.
The second footnote is the one that explains the persistence without spelling out the mechanism. Under the hood the Store is doing the work of saving all changes, and the point of the footnote is that the caller does not have to.
The Demo directory in the repository exists for this reason, since the README points to a demo app built on the Model View Controller Store architecture, and to a blog post exploring the MVCS architecture thinking behind it. The library is named after that architecture, and the demo shows what an offline-ready SwiftUI app looks like in practice rather than in isolated snippets.
Observability comes from @Observable plus an events stream
Realtime updates are handled in two layers, and the first one is free because of how the types are declared.
The Store, @StoredValue and @SecurelyStoredValue are all @Observable, so you can subscribe to changes in realtime using any of Swift's built-in observability mechanisms. The README's example is the SwiftUI onChange modifier:
.onChange(of: store.items) {
self.items = self.items.sorted(by: { $0.createdAt > $1.createdAt})
})This is the part of the design that makes the library feel native rather than bolted on. If the store is an @Observable type, the standard SwiftUI observation machinery handles view invalidation, and there is no custom subscription type to learn and no separate publisher to wire into your views.
The second layer is more granular. The Granular Events Tracking API notifies you of individual changes rather than of a changed array, and the example iterates over an async stream of events on the controller's notes property:
func monitorNotesStoreEvents() async {
for await event in self.notesController.$notes.events {
switch event.operation {
case .initialized:
print("[Store Event: initial] Our Notes Store has initialized")
case .loaded:
print("[Store Event: loaded] Our Notes Store has loaded with notes", event.notes.map(\.text))
case .insert:
print("[Store Event: insert] Our Notes Store inserted notes", event.notes.map(\.text))
case .remove:
print("[Store Event: remove] Our Notes Store removed notes", event.notes.map(\.text))
}
}
}The event operation enum has the four states you would expect: initialized, loaded, insert and remove, and the loaded event carries the items that were loaded, so a view can distinguish an empty store from one that has not finished loading. That distinction is exactly the thing that causes flicker in a naive implementation, and having it as a first class event is a thoughtful detail.
For a caching layer that runs offline-first, the difference between initialized and loaded is not cosmetic. An app that renders an empty list before the disk cache has been read will show an empty state and then pop content in, and the events API lets you avoid that.
Bodega underneath, and an ecosystem that includes agent plugins
The repository layout shows a Swift package with a lot of adjacent tooling, and one directory is unexpected.
There is Sources/ for the library, Tests/, a Demo/ directory containing a full Xcode project, and docs/. Package.swift and Package.resolved are at the root, along with an .spi.yml for the Swift Package Index, a .swiftpm/ directory, and an Images/ folder. The Performance Profiler directory is a space, not a hyphen, which is a small clue that the project is maintained by someone who is comfortable with a Mac and its conventions.
The unusual directories are .agents/, .claude-plugin/ and plugins/, along with an llms.txt file at the root. That is a Coding Agent Plugins feature, and the README's table of contents lists it as its own section. The topic list on the repository includes actors, appkit, cache, database, ios, macos, offline-first, persistence, swift, swiftui and uikit, and the actors tag is worth noting because the Store and its event streams are actor-based, which is what makes the async insert and remove calls safe to fire from anywhere in your app.
The llms.txt file is a convention for making a repository readable by a language model, and a .claude-plugin/ directory is the packaging format for a Claude Code plugin. For a project whose selling point is a small API, shipping a skill file that teaches an assistant the API is a coherent extension of the same idea, and it is rare enough to be worth knowing about.
The tests directory is at the top level rather than inside Sources, which is the Swift Package Manager convention, and the presence of a Demo Xcode project with its own LICENSE and README means the demo is a self-contained project you can open and run.
MIT licensed, version 3, and a maintenance pace worth checking
The licence is MIT, with the LICENSE file in the repository as the authoritative text, and this is a licensing statement rather than legal advice.
The release record shows a 3.x line with a slow cadence. The recent releases are 3.0.2, whose release name is You Got Skills, on 2026-05-05, 3.0.1 named We all should be a little more dynamic on 2025-04-18, and 3.0.0 named Boutique 3.0MG on 2025-04-08. The naming convention of putting a sentence in the release tag is a small thing that tells you about the project, and 3.0.2 being called You Got Skills lines up with the agent plugin support arriving in that release.
The last push was on 2026-07-13, and the repository is not archived. That is a recent push, and the 3.x line has been in place since April 2025.
The documentation is split in two, and the split is worth knowing about. The high level overview is in the README, while the full API documentation is hosted on the mergesort.github.io site, and the Store in particular is documented with context, use cases and examples under its own using-stores section. The homepage given for the project is build.ms/boutique/docs, and the blog post linked from the README explores the MVCS architecture thinking behind the design.
The README also asks for sponsorship, and says that if you find Boutique valuable it would be appreciated if you would consider sponsoring the open source work so the work can continue on projects like Boutique. For a single-maintainer library that a lot of app developers depend on, that is the funding model, and it is worth knowing that the roadmap is a person's availability rather than a company's.
The README's final section asks for feedback and contribution, and the presence of a CONTRIBUTING-style section means there is a path for someone who wants to help rather than only use it.
Editorial conclusion
Adopt Boutique if you build Apple platform apps and want persisted, realtime-updating model collections without designing a database schema or writing a repository layer. Do not adopt it if you are committed to Core Data, since the quoted reaction it exists to replace is precisely that. Before you start, read the Bodega StorageEngine documentation, because the storage engine is the part you configure, and decide how many Stores your app needs, since the library is deliberately not prescriptive about that.
Frequently asked questions
What is Boutique and what does it replace?
It is a small set of Swift property wrappers and types that enable building state-driven apps for SwiftUI, UIKit and AppKit, with a dual-layered memory and disk caching architecture. The README quotes Josh Holtz describing it as the way it should work after ripping out Core Data, and the package version is 3.0.2.
How do I create a Store and insert models with Boutique?
Create a Store generic over your model with a storage engine and a cache identifier, for example Store<Animal>(storage: SQLiteStorageEngine.default(appendingPath: "Animals"), cacheIdentifier: \.id), then call try await store.insert(model). The Store handles uniqueness itself, so inserting a second model with the same identifier does not duplicate it.
How does Boutique handle realtime updates in SwiftUI?
The Store, @StoredValue and @SecurelyStoredValue are all @Observable, so you can subscribe with Swift's built-in observability mechanisms such as .onChange(of: store.items). For finer detail there is a Granular Events Tracking API with an async stream of events whose operation is one of initialized, loaded, insert or remove.
What is Bodega and how does it relate to Boutique?
Boutique is built atop Bodega, which provides the storage engine abstraction and caching machinery, while Boutique adds the Swift-facing Store, property wrappers and ergonomics. The README notes that the choice of how many Stores to use, and the way to model your data, is deliberately left to you.
Is Boutique actively maintained and under what licence?
The repository is not archived and the last push was on 2026-07-13, with releases 3.0.2 on 2026-05-05, 3.0.1 on 2025-04-18 and 3.0.0 on 2025-04-08. The licence is MIT, with the LICENSE file in the repository as the authoritative text.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mergesort-boutique)Was this useful?