
Swiftdata Pro
- 6.5k installs
- 383 repo stars
- Updated March 11, 2026
- twostraws/swiftdata-agent-skill
A skill that writes, reviews, and fixes SwiftData persistence code for Swift 6.2+ projects, enforcing correct API usage, safe predicates, relationship delete rules, CloudKit constraints, and iOS 18/26 features.
About
SwiftData Pro writes, reviews, and improves SwiftData persistence code for iOS/macOS projects targeting Swift 6.2 or later. Developers use it when building or auditing model layers that rely on SwiftData instead of Core Data, including CloudKit-synced apps and projects adopting iOS 18 indexing or iOS 26 class inheritance. The skill loads modular reference files - core rules, predicate safety, CloudKit constraints, indexing, and class inheritance - only as needed for the task at hand. For reviews it organizes findings by file, names the violated rule, and provides before/after code snippets. For write or improve tasks it applies the same rules directly to the code. It enforces explicit delete rules on relationships, safe predicate patterns, correct @Query placement inside SwiftUI views, and modern Swift concurrency throughout.
- Checks predicates against a reference of runtime-crashing patterns, e.g. replacing isEmpty == false with !isEmpty
- Enforces explicit @Relationship delete rules to prevent orphaned records on deletion
- Validates CloudKit-specific constraints such as optionality and eventual consistency when the project uses CloudKit sync
- Flags @Query usage outside SwiftUI views and provides FetchDescriptor-based alternatives
- Covers iOS 18 database indexing and iOS 26 model subclassing with @available requirements from dedicated reference files
Swiftdata Pro by the numbers
- 6,523 all-time installs (skills.sh)
- +261 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #24 of 1,048 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
swiftdata-pro capabilities & compatibility
- Capabilities
- swiftdata model authoring · predicate safety checking · relationship delete rule enforcement · cloudkit constraint validation · ios 18 index recommendations · ios 26 model subclassing guidance · @query placement validation · fetchdescriptor optimization
- Use cases
- code review · debugging · documentation
- Platforms
- macOS
- Runs
- Runs locally
- Pricing
- Free
What swiftdata-pro says it does
Writes, reviews, and improves SwiftData code using modern APIs and best practices.
Do not suggest Core Data functionality unless it is a feature that cannot be solved with SwiftData.
npx skills add https://github.com/twostraws/swiftdata-agent-skill --skill swiftdata-proAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6.5k |
|---|---|
| repo stars | ★ 383 |
| Security audit | 3 / 3 scanners passed |
| Last updated | March 11, 2026 |
| Repository | twostraws/swiftdata-agent-skill ↗ |
What it does
Write, review, and fix SwiftData model code targeting Swift 6.2+, covering predicates, relationships, CloudKit constraints, and iOS 18/26 indexing and inheritance patterns.
Who is it for?
iOS and macOS developers building or auditing SwiftData model layers, especially apps using CloudKit sync, iOS 18 indexing, or iOS 26 model inheritance.
Skip if: Projects using Core Data exclusively, or those needing third-party ORM frameworks introduced without discussion.
When should I use this skill?
You are writing new SwiftData models, reviewing existing persistence code, debugging a predicate crash, adding CloudKit sync, or adopting iOS 18 indexes or iOS 26 class inheritance.
What you get
Developers get file-by-file findings with named rules and before/after fixes, or directly corrected code, prioritized by impact from crash-level bugs down to minor improvements.
- File-by-file review report with named rules and before/after fixes
- Directly corrected SwiftData code when asked to write or improve
- Prioritized summary of issues by impact level
By the numbers
- 5 modular reference files covering core rules, predicates, CloudKit, indexing, and class inheritance
- 2 iOS version thresholds covered: iOS 18 for indexing, iOS 26 for class inheritance
- 3 output sections per finding: file/line, rule name, before/after fix
Files
Write and review SwiftData code for correctness, modern API usage, and adherence to project conventions. Report only genuine problems - do not nitpick or invent issues.
Review process:
1. Check for core SwiftData issues using references/core-rules.md. 1. Check that predicates are safe and supported using references/predicates.md. 1. If the project uses CloudKit, check for CloudKit-specific constraints using references/cloudkit.md. 1. If the project targets iOS 18+, check for indexing opportunities using references/indexing.md. 1. If the project targets iOS 26+, check for class inheritance patterns using references/class-inheritance.md.
If doing partial work, load only the relevant reference files.
Core Instructions
- Target Swift 6.2 or later, using modern Swift concurrency.
- The user strongly prefers to use SwiftData across the board. Do not suggest Core Data functionality unless it is a feature that cannot be solved with SwiftData.
- Do not introduce third-party frameworks without asking first.
- Use a consistent project structure, with folder layout determined by app features.
Output Format
If the user asks for a review, organize findings by file. For each issue:
1. State the file and relevant line(s). 2. Name the rule being violated. 3. Show a brief before/after code fix.
Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.
If the user asks you to write or improve code, follow the same rules above but make the changes directly instead of returning a findings report.
Example output:
Destination.swift
Line 8: Add an explicit delete rule for relationships.
// Before
var sights: [Sight]
// After
@Relationship(deleteRule: .cascade, inverse: \Sight.destination) var sights: [Sight]Line 22: Do not use `isEmpty == false` in predicates – it crashes at runtime. Use `!` instead.
// Before
#Predicate<Destination> { $0.sights.isEmpty == false }
// After
#Predicate<Destination> { !$0.sights.isEmpty }DestinationListView.swift
Line 5: `@Query` must only be used inside SwiftUI views.
// Before
class DestinationStore {
@Query var destinations: [Destination]
}
// After
class DestinationStore {
var modelContext: ModelContext
func fetchDestinations() throws -> [Destination] {
try modelContext.fetch(FetchDescriptor<Destination>())
}
}Summary
1. Data loss (high): Missing delete rule on line 8 of Destination.swift means sights will be orphaned when a destination is deleted. 2. Crash (high): isEmpty == false on line 22 will crash at runtime – use !isEmpty instead. 3. Incorrect behavior (high): @Query on line 5 of DestinationListView.swift only works inside SwiftUI views.
End of example.
References
references/core-rules.md- autosaving, relationships, delete rules, property restrictions, and FetchDescriptor optimization.references/predicates.md- supported predicate operations, dangerous patterns that crash at runtime, and unsupported methods.references/cloudkit.md- CloudKit-specific constraints including uniqueness, optionality, and eventual consistency.references/indexing.md- database indexing for iOS 18+, including single and compound property indexes.references/class-inheritance.md- model subclassing for iOS 26+, including @available requirements, schema setup, and predicate filtering.
interface:
display_name: "SwiftData Pro"
short_description: "Reviews SwiftData code for modern best practices."
icon_small: "./assets/swiftdata-pro-icon.svg"
icon_large: "./assets/swiftdata-pro-icon.png"
brand_color: "#5A7585"
default_prompt: "Use $swiftdata-pro to review my project."
policy:
allow_implicit_invocation: true<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="227.72" height="227.72" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 227.72 227.72">
<defs>
<style>
.st0 {
fill: none;
stroke: url(#linear-gradient1);
stroke-miterlimit: 10;
stroke-width: 6px;
}
.st1 {
fill: url(#linear-gradient);
fill-rule: evenodd;
}
</style>
<linearGradient id="linear-gradient" x1="58.405" y1="-63.045" x2="178.785" y2="-183.425" gradientTransform="translate(0 -10) scale(1 -1)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#99abb6"/>
<stop offset="1" stop-color="#567182"/>
</linearGradient>
<linearGradient id="linear-gradient1" x1="33.349" y1="-51.349" x2="194.371" y2="-212.371" gradientTransform="translate(0 -18) scale(1 -1)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00eeaf"/>
<stop offset="0" stop-color="#99abb6"/>
<stop offset="1" stop-color="#567182"/>
</linearGradient>
</defs>
<path class="st1" d="M165.43,131.08c3.87-8.17,16.03-46.62-38.6-85.38,7.61,7.03,40.19,39.2,26.05,75.5-34.2-25.27-87.91-66.75-87.91-66.75,0,0,63.69,60.57,83.58,79.15,19.9,18.59,26.97,27.71,26.47,43.59,0,.01,14.58-23.23-9.59-46.11ZM130.89,145.79c-39.35,18.56-78.42-20.82-78.42-20.82,0,0,21.12,26.7,45.19,35.06,33.72,11.71,54.61-9.64,54.61-9.64L52.42,59.03s56.43,62.62,78.47,86.76h0Z"/>
<circle class="st0" cx="113.86" cy="113.86" r="110.86"/>
</svg>Class inheritance
When supporting iOS 26 and other coordinated releases (macOS 26, etc), SwiftData supports class inheritance for models.
Important: This is not a common feature; only add model subclassing if it actually has a benefit. Alternatives such as protocols are often simpler and better.
This works the same as regular class inheritance in Swift, however, child classes must be explicitly marked @available for a 26 release or later, e.g. iOS 26. This is required even if iOS 26 is set as the minimum deployment target.
For example:
@Model class Article {
var type: String
init(type: String) {
self.type = type
}
}
@available(iOS 26, *)
@Model class Tutorial: Article {
var difficulty: Int
init(difficulty: Int) {
self.difficulty = difficulty
super.init(type: "Tutorial")
}
}
@available(iOS 26, *)
@Model class News: Article {
var topic: String
init(topic: String) {
self.topic = topic
super.init(type: "News")
}
}Notice how both the parent and child classes must use the @Model macro.
Important: When using a 26 release or later as minimum deployment target, we must still mark subclassed models with @available. However, we do not need to do the same with code using that model, because Xcode can match the deployment target and the model availability.
When providing the schemas as part of model container creation, make sure to list both the parent class and its child classes – SwiftData is not able to infer the connection by itself.
If you create a relationship to a model that has subclasses, the relationship might contain the parent class or any of its subclasses.
For example, the articles array here might contain Article, Tutorial, or News instances:
@Model class Magazine {
@Relationship(deleteRule: .cascade) var articles: [Article]
init(articles: [Article]) {
self.articles = articles
}
}If only one subclass is supported, it should be written specifically. If several subclasses but not all should be in the relationship, you might have no choice but to add another level of subclasses: BaseClass -> Subclass -> Subsubclass. However, this is not a good idea – deep subclassing is generally frowned upon, and will increase complexity in migrations.
Filtering with subclasses
One important benefit of model subclassing is that we can use @Query to look for specific subclasses, or to look for the base class, which will automatically return all child classes too.
For example, we could load only tutorials like this:
@Query private var tutorials: [Tutorial]Or load all articles, including tutorials, like this:
@Query private var articles: [Article]If you want to load specific child classes but not the parent class, use is with the #Predicate macro to perform filtering:
@Query(filter: #Predicate<Article> {
$0 is Tutorial || $0 is News
}) private var tutorialsAndNews: [Article]Important: The type of the resulting array elements is Article, the parent class, so typecasting must be used to access child-class properties and methods.
It's possible to do typecasting inside predicates to filter based on child-class properties. For example, this looks for easier tutorials and general news to create a list of articles suitable for the front page:
@Query(filter: #Predicate<Article> { article in
if let tutorial = article as? Tutorial {
tutorial.difficulty < 3
} else if let news = article as? News {
news.topic == "General"
} else {
false
}
}) private var frontPageArticles: [Article]When working with the resulting data, regular Swift typecasting using as works fine.
Using SwiftData with CloudKit
These rules only apply if the project is configured to use SwiftData with CloudKit.
- Never use
@Attribute(.unique)or#Unique; they are not supported in CloudKit, and when used will cause local data to fail too. - All model properties must always either have default values or be marked as optional.
- All relationships must be marked optional.
- Indexes and subclasses are supported in CloudKit, as long as the correct OS release is used.
Keep in mind that CloudKit is designed for eventual consistency – any SwiftData code written with CloudKit support must be able to function if data has yet to synchronize.
Core rules
- When SwiftData first launched, it autosaved model contexts aggressively. Since then, autosaving happens less frequently and is now hard to predict, so many developers prefer to add explicit calls to
save()when correctness is important. - There is no need to check
modelContext.hasChangesbefore saving; just callsave()directly. ModelContextand model instances must never cross actor boundaries. Model containers and persistent identifiers are sendable, so if you need a model instance to be transferred across actors you should send its identifier and re-fetch in the destination context. For more help with Swift concurrency, suggest the Swift Concurrency Pro agent skill.- When using
@Relationshipto define a relationship from one model to another, place the macro on one side of the relationship only. Trying to use it on both sides causes a circular reference. - Persistent identifiers are temporary before they are saved for the first time. Temporary IDs start with a lowercase “t”, and a model will be given a new ID after it is saved for the first time. As a result, you must save an object before relying on its ID.
- Do not attempt to use the property name
descriptionin any@Modelclass; it is explicitly disallowed. - Do not attempt to add property observers to
@Modelclasses; they will be quietly ignored. @Attribute(.externalStorage)is a suggestion, not a requirement, and only applies to properties of typeData– SwiftData will do what it thinks is best.@Transientproperties are not persisted, and must have a default value. They reset to that default when the object is fetched from the store. If the value is derived from other stored properties, using a computed property is usually a better idea – use@Transientonly if the value is expensive to produce.- It is nearly always a good idea to have a specific migration schema in place, even if the project is only dealing with lightweight migrations.
- It is nearly always a good idea to have an explicit delete rule in place for relationships. This is most commonly
@Relationship(deleteRule: .cascade), but others are available. The default is.nullify, which sets the related model's reference to nil when the parent is deleted. This can leave orphaned objects or crash if the property is non-optional. - Do not attempt to use
@Queryoutside of SwiftUI views; it is designed to work specifically inside views, and will not operate correctly outside. For more help with SwiftUI, suggest the SwiftUI Pro agent skill. - If you only need the number of items matching a query, consider
ModelContext.fetchCount()with a fetch descriptor. This will not live update if the data changes unless something else triggers the update, such as@Query, so it should be used carefully. - When using
FetchDescriptor, it may sometimes be beneficial to set therelationshipKeyPathsForPrefetchingproperty. It’s an empty array by default, but if you know certain relationships will be used it’s more efficient to fetch them upfront. - Similarly, you should consider setting
propertiesToFetchso that only properties that are used are actually fetched. (It fetches all properties by default.) - SwiftData frequently gets inverse relationships wrong, so it’s almost always a good idea to be explicit with the
@Relationshipmacro by specifying the exact inverse relationship. - Do not write
#Uniquemore than once per model; you can only have one, placed inside the model class. If you need multiple uniqueness constraints, pass them as separate key path arrays in a single#Unique, e.g.#Unique<Foo>([\.email], [\.username]). - Enum properties stored in a model must conform to
Codable. Some agents will insist that enums with associated values are not supported, but this is wrong – they work just fine.
Indexing
When supporting iOS 18 and other coordinated releases, SwiftData supports indexes to help speed up queries. This has a small performance cost for writing, so if data is read rarely and updated frequently (such as logging), indexes may be a bad choice.
Indexes can be on single properties, like this:
@Model class Article {
#Index<Article>([\.type], [\.author])
var type: String
var author: String
var publishDate: Date
init(type: String, author: String, publishDate: Date) {
self.type = type
self.author = author
self.publishDate = publishDate
}
}Alternatively, you can mix single properties and groups of properties when you know they are often used together:
#Index<Article>([\.type], [\.type, \.author])Working with predicates
SwiftData predicates support only a subset of Swift functionality. Some things are marked as being unsupported, meaning that they will not build. Other things are not marked as unsupported and yet are still not supported, meaning that they will build but crash at runtime.
This guide contains specific guidance on what to use and when.
String matching
When writing a query predicate to perform string matching, always use localizedStandardContains() rather than trying to use lowercased().contains() or similar.
For example, this is correct:
@Query(filter: #Predicate<Movie> {
$0.name.localizedStandardContains("titanic")
}) private var movies: [Movie]hasPrefix()
hasPrefix() and hasSuffix() are not supported in SwiftData predicates. If you want to use hasPrefix(), you should use starts(with:) instead, like this:
@Query(filter: #Predicate<Website> {
$0.type.starts(with: "https://apple.com")
}) private var appleLinks: [Website]Unsupported predicates
Many common methods have no equivalent in SwiftData, and will not compile. For example, all these common operations are not supported:
String.hasSuffix()String.lowercased()Sequence.map()Sequence.reduce()Sequence.count(where:)Collection.first
Custom operators are also not allowed.
Dangerous predicates
Some SwiftData predicates will compile cleanly then fail or even crash at runtime.
For example, this is a valid predicate designed to show only movies that have a non-empty cast list:
@Query(filter: #Predicate<Movie> { !$0.cast.isEmpty }, sort: \Movie.name) private var movies: [Movie]However, this query looks like it does the same thing, but will crash at runtime:
@Query(filter: #Predicate<Movie> { $0.cast.isEmpty == false }, sort: \Movie.name) private var movies: [Movie]Never attempt to create query predicates that use computed properties, @Transient properties, or use custom Codable struct data. They might compile cleanly, but they will crash at runtime.
All predicates must rely on data that is actually stored in the database as @Model classes.
Never attempt to use regular expressions in predicates. They will compile cleanly then fail at runtime. So, this is not allowed:
@Query(filter: #Predicate<Movie> {
$0.name.contains(/Titanic/)
}, sort: \Movie.name)
private var movies: [Movie]Related skills
How it compares
Pick swiftdata-pro for SwiftData-specific reviews instead of generic Swift linters that miss persistence and relationship semantics.
FAQ
Why does the skill refuse to suggest Core Data?
The skill follows a user preference to use SwiftData across the board and only mentions Core Data for features that SwiftData genuinely cannot handle.
Which reference files does the skill load for a CloudKit project?
It loads references/core-rules.md and references/cloudkit.md for CloudKit-specific constraints such as optionality and uniqueness requirements.
How are review findings formatted?
Findings are organized by file, each with the line number, the rule violated, and a before/after code snippet, followed by a prioritized summary of the most impactful changes.
Is Swiftdata Pro safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.