# square/Valet: Keychain Storage Without the Keychain API

> Valet wraps the iOS, tvOS, watchOS and macOS Keychain behind a small Swift and Objective-C API. It is a good fit for apps that need key:value secrets with a defined accessibility policy, and a poor fit for teams that want the raw SecItem API or cross-platform credential stores.

**square/Valet** — Valet lets you securely store data in the iOS, tvOS, watchOS, or macOS Keychain without knowing a thing about how the Keychain works. It’s easy. We promise.

- Repository: https://github.com/square/Valet
- Stars: 4,170 · Forks: 227
- Language: Swift
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/square-valet

## The Keychain API Valet Is Trying to Replace

The Apple Keychain is a C API built around dictionaries. You assemble a query of CFString keys, call SecItemAdd, SecItemCopyMatching, SecItemUpdate or SecItemDelete, and interpret OSStatus codes. The attribute set is large and the failure modes are not obvious: an accessibility attribute chosen at write time governs when the item can be read later, and a mismatch between the query used to write and the query used to read returns itemNotFound rather than an error that explains itself.

Valet's premise is that most apps need a key:value store with a stated accessibility policy, not a general query engine. The README describes the project as letting you store data in the Keychain "without knowing a thing about how the Keychain works." The audience is Apple-platform developers writing Swift or Objective-C who want the Keychain's protection without writing SecItem plumbing by hand. The repository ships test host apps for iOS, macOS, tvOS and watchOS, which indicates the supported surface is the four Apple platforms named in the description, not Linux or server-side Swift.

## How a Valet Instance Sandboxes Your Data

A Valet is constructed from three things: an identifier, an accessibility value, and the initializer used. The README states that these are combined to create a sandbox within the Keychain, and that two Valets created with the same type, initializer, accessibility value and identifier read and write the same key:value pairs, while Valets with different identifiers each get their own sandbox. That is the core mechanism. There is no separate namespace argument and no database file; the identifier is the namespace.

The accessibility value is an enum that the README says defines when data can be persisted and retrieved. The guidance in the README is to pick the strictest value that still lets the app function, and it gives the example that an app which does not run in the background should use .whenUnlocked or .whenUnlockedThisDeviceOnly. Because accessibility is part of the sandbox identity, changing it after data exists does not move the data. The README is explicit on this point: you must migrate key:value pairs from the old accessibility Valet to the new one to avoid data loss. That is a design consequence worth internalizing before you pick a value, because the choice is not a runtime toggle.

For apps that need to share secrets across products from the same developer, Valet offers a shared group initializer that takes an app ID prefix and a group name, matching the Keychain sharing entitlement model. The README's example uses SharedGroupIdentifier(appIDPrefix: "AppID12345", nonEmptyGroup: "Druidia").

## Installing Valet and Storing Your First Secret

Valet is distributed through Swift Package Manager, CocoaPods, Carthage, or as a checked-out submodule dragged into an Xcode project. The README lists all four. Swift Package Manager is the shortest path: add the dependency to Package.swift, pinned from 5.0.0 in the README example.

```swift
dependencies: [
    .package(url: "https://github.com/Square/Valet", from: "5.0.0"),
],
```

CocoaPods users add the pod to the Podfile instead. The README gives the version constraint as ~> 5.0.0.

```
pod 'Valet', '~> 5.0.0'
```

Once the dependency resolves, create a Valet with an identifier and an accessibility value. The README's example uses "Druidia" and .whenUnlocked. The Swift API wraps the identifier in an Identifier type that rejects empty strings, so you supply a non-empty string and the wrapper enforces the constraint.

```swift
let myValet = Valet.valet(with: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
```

Writing and reading are two calls. The README stores a string under a username key and reads it back with string(forKey:). Valet also exposes setObject(_:forKey:) and object(forKey:) for Data, which is what you would use for tokens or binary blobs.

```swift
let username = "Skroob"
try? myValet.setString("12345", forKey: username)
let myLuggageCombination = myValet.string(forKey: username)
```

After that snippet runs, myLuggageCombination holds "12345" as long as the device is unlocked and the same Valet parameters are used for the read. If you later decide the accessibility value was wrong, the README shows the migration path: build a second Valet with the new accessibility and call migrateObjects(from:removeOnCompletion:), passing the old Valet as the source.

```swift
let myOldValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
let myNewValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .afterFirstUnlock)
try? myNewValet.migrateObjects(from: myOldValet, removeOnCompletion: true)
```

## The macOS Identifier Trap and the Uniqueness Guarantee

On macOS, the README notes that apps signed with a developer ID may see their Valet's identifier shown to users, and it links to issue 140 for context. To make that string friendlier, Valet provides an initializer that sets the identifier explicitly. The README marks this with warning symbols and states plainly that doing so bypasses the project's guarantee that one Valet type will not have access to another type's key:value pairs. The stated mitigation is to ensure each Valet's identifier is globally unique.

That is the sharpest trade-off in the library. The default identifier derivation gives you isolation between Valet types for free. The explicitly-set initializer gives you a presentable string in exchange for taking on the uniqueness burden yourself. If two Valets in the same app end up with the same explicit identifier and the same accessibility, they share a sandbox, and the failure is silent: one component reads another's values and nothing throws. The README does not describe a runtime check that would catch a collision. Treat the explicitly-set initializer as a deliberate exception, not the default, and audit for duplicate identifier strings if you use it.

## Where Valet Stops Being the Right Tool

Valet is scoped to Apple platforms and to the Keychain. If your app also ships on Android, Windows or the web, Valet does nothing for those targets, and a shared secret across platforms needs a different store. The repository layout reflects this: the test host apps are all Apple platform projects, and there is no server-side component.

A second boundary is control. Valet's value comes from hiding SecItem query construction. If you need a query shape the library does not expose, or you are working with Keychain item classes outside the key:value model, the abstraction works against you. You would be reading Valet's source to find out what it does under the hood anyway, which is the cost the library was meant to remove.

A third boundary is migration discipline. Because accessibility and identifier are part of the sandbox, any change to either is a data migration. The README documents migrateObjects(from:removeOnCompletion:) for the accessibility case, but it does not document a rollback path if a migration fails partway. An app that changes accessibility on a schedule, or that lets users toggle a biometric requirement, has to own that migration logic and its failure handling.

## Valet Against Using SecItem Directly

The real alternative is not another library. It is writing SecItem calls yourself, or using a different wrapper with a different model. The difference in approach is where the policy lives.

With raw SecItem, you construct the query dictionary at each call site. Accessibility, access group and synchronizable flags are per-call decisions, which means the policy is distributed across the codebase and can drift between the write path and the read path. Valet moves those decisions into the Valet instance: you pick the accessibility once, at construction, and every subsequent read and write inherits it. The trade is that changing the policy later requires migration rather than simply passing a different flag on the next call.

A second difference is error surface. Raw SecItem returns OSStatus values that you map to errors yourself. Valet's Swift API uses throwing methods and optional-returning reads, as the README's try? and optional binding show. That is less ceremony, and it is also less information: the README's examples discard errors with try?, which is fine for a snippet and wrong for production code where a failed write should be surfaced.

There is no cross-platform credential store in this comparison. If you need one, Valet is the wrong shelf, not the wrong brand.

## Maintenance, Upgrades and the Apache-2.0 Licence

The repository is not archived, and the last push was on 2026-09-09, the same day release 5.1.1 was published. The release line shows 5.0.1 in February 2026, 5.1.0 later that month, and 5.1.1 in September 2026, so the project has shipped patch and minor releases within the current year. That is the extent of what the release data supports; it says nothing about how quickly issues are answered.

On upgrades, the README's install instructions pin from 5.0.0 for Swift Package Manager and ~> 5.0.0 for CocoaPods. An app that adopts Valet inherits the cost of keeping that constraint current, and the accessibility and identifier semantics described above mean a major version bump is the moment to re-check whether the sandbox parameters your app relies on have changed. The README does not describe a deprecation policy or a version-to-version migration guide beyond the accessibility migration call.

The licence is Apache-2.0, which the README surfaces through a badge linking to the SPDX identifier. Apache-2.0 is a permissive licence with an explicit patent grant and a notice requirement. This is not legal advice; if your organization has rules about attribution files or about shipping permissive-licensed dependencies, route the LICENSE file through that process rather than assuming the badge settles it.

## Conclusion

Adopt Valet if your Apple-platform app needs key:value secrets with an explicit accessibility policy and you are willing to treat accessibility changes as migrations. Do not adopt it if you need a cross-platform credential store, or if you want direct control over SecItem queries. Before writing code, verify three things: that your target platforms are covered by the package, that your chosen identifier is unique per Valet sandbox, and that any accessibility change is paired with migrateObjects(from:removeOnCompletion:) so existing key:value pairs are not stranded.

## FAQ

### How do I install square/Valet in a Swift project?

Add the package to your Package.swift dependencies using the URL https://github.com/Square/Valet with a version constraint from 5.0.0, as the README shows. CocoaPods, Carthage and a manual submodule checkout are also documented in the README.

### What happens if I change a Valet's accessibility value after storing data?

The key:value pairs do not move, because accessibility is part of the sandbox identity. The README says you must migrate them from the old Valet to the new one, and shows migrateObjects(from:removeOnCompletion:) for that purpose.

### Does square/Valet work on Android or on a server?

No. Valet targets the Apple Keychain on iOS, tvOS, watchOS and macOS, and the repository's test host apps are all Apple platform projects. There is no cross-platform or server-side component in the project.

### Why does the README warn about setting a Valet identifier explicitly on macOS?

Mac apps signed with a developer ID may show the identifier to users, so Valet offers an initializer to set it explicitly. The README warns that this bypasses the guarantee that one Valet type cannot access another type's key:value pairs, and says each identifier must then be globally unique.

## Sources

- [Issues](https://github.com/square/Valet/issues)
- [License: Apache-2.0](https://github.com/square/Valet/blob/main/LICENSE)
- [README](https://github.com/square/Valet/blob/main/README.md)
- [Releases](https://github.com/square/Valet/releases)
- [square/Valet on GitHub](https://github.com/square/Valet)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/square-valet
