Skip to content

Database Schema

Last Updated: 2026-02-03 | Reading Time: 15 minutes

CoreData model documentation for PasteShelf.



PasteShelf uses CoreData with CloudKit integration for data persistence. The schema is designed to:

  • Efficiently store diverse clipboard content types
  • Support fast full-text search
  • Enable seamless CloudKit sync (Pro)
  • Handle large binary data (images, files)
  • Maintain privacy with encryption support
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Data Store Architecture β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ NSPersistentCloudKitContainer β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ Local Store β”‚ β”‚ CloudKit Store ⭐ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ ──────────── β”‚ β”‚ ───────────────── β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ ~/Library/ β”‚ β”‚ iCloud Private DB β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Application β”‚ β”‚ Custom Zone β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Support/ β”‚ β”‚ E2E Encrypted β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ PasteShelf/ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ PasteShelf.sqlite β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ Store Configuration: β”‚
β”‚ β€’ Local: Always available, primary storage β”‚
β”‚ β€’ Cloud: Pro/Enterprise only, synced with iCloud β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Entity Relationship Diagram β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Application β”‚ β”‚
β”‚ β”‚ (excluded) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”‚ excludedApp β”‚
β”‚ β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Folder │◀────────│ ClipboardItem │────────▢│ Tag β”‚ β”‚
β”‚ β”‚ β”‚ folder β”‚ (Core) β”‚ tags β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ subfoldersβ”‚ β”‚ β”‚ β”‚ items β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β–Ό β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ClipboardContentβ”‚ β”‚ ContentPreview β”‚ β”‚
β”‚ β”‚ (Binary) β”‚ β”‚ (Thumbnail) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ UserPreference β”‚ β”‚ SearchIndex β”‚ β”‚ Action ⭐ β”‚ β”‚
β”‚ β”‚ (Settings) β”‚ β”‚ (Search Cache) β”‚ β”‚ (Automation) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The primary entity storing clipboard history entries.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ClipboardItem β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key, unique β”‚
β”‚ createdAt Date When item was captured β”‚
β”‚ modifiedAt Date Last modification time β”‚
β”‚ accessedAt Date Last paste/view time β”‚
β”‚ accessCount Int32 Number of times accessed β”‚
β”‚ β”‚
β”‚ contentType String UTI type identifier β”‚
β”‚ contentHash String SHA-256 hash for dedup β”‚
β”‚ textContent String? Searchable text (indexed) β”‚
β”‚ plainText String? Plain text representation β”‚
β”‚ β”‚
β”‚ sourceApp String? Bundle ID of source app β”‚
β”‚ sourceURL String? URL if copied from browser β”‚
β”‚ title String? Auto-generated or user title β”‚
β”‚ β”‚
β”‚ isFavorite Bool User marked as favorite β”‚
β”‚ isPinned Bool Pinned to top β”‚
β”‚ isSensitive Bool Contains sensitive data β”‚
β”‚ isEncrypted Bool Content is encrypted β”‚
β”‚ β”‚
β”‚ RELATIONSHIPS β”‚
β”‚ ───────────── β”‚
β”‚ β”‚
β”‚ content ClipboardContent 1:1 Binary content β”‚
β”‚ preview ContentPreview 1:1 Thumbnail β”‚
β”‚ folder Folder? N:1 Optional folder β”‚
β”‚ tags [Tag] N:N Tag associations β”‚
β”‚ β”‚
β”‚ INDEXES β”‚
β”‚ ─────── β”‚
β”‚ β”‚
β”‚ β€’ createdAt (descending) - Default sort β”‚
β”‚ β€’ textContent (full-text search) β”‚
β”‚ β€’ contentHash (deduplication) β”‚
β”‚ β€’ contentType (filtering) β”‚
β”‚ β€’ isFavorite, isPinned (quick access) β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

CoreData Definition:

ClipboardItem+CoreDataClass.swift
@objc(ClipboardItem)
public class ClipboardItem: NSManagedObject {
@NSManaged public var id: UUID
@NSManaged public var createdAt: Date
@NSManaged public var modifiedAt: Date
@NSManaged public var accessedAt: Date
@NSManaged public var accessCount: Int32
@NSManaged public var contentType: String
@NSManaged public var contentHash: String
@NSManaged public var textContent: String?
@NSManaged public var plainText: String?
@NSManaged public var sourceApp: String?
@NSManaged public var sourceURL: String?
@NSManaged public var title: String?
@NSManaged public var isFavorite: Bool
@NSManaged public var isPinned: Bool
@NSManaged public var isSensitive: Bool
@NSManaged public var isEncrypted: Bool
@NSManaged public var content: ClipboardContent?
@NSManaged public var preview: ContentPreview?
@NSManaged public var folder: Folder?
@NSManaged public var tags: Set<Tag>
}

Stores the actual binary content separately for performance.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ClipboardContent β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key β”‚
β”‚ data Binary Raw content data β”‚
β”‚ mimeType String MIME type β”‚
β”‚ size Int64 Size in bytes β”‚
β”‚ isCompressed Bool Compression applied β”‚
β”‚ β”‚
β”‚ RELATIONSHIPS β”‚
β”‚ ───────────── β”‚
β”‚ β”‚
β”‚ item ClipboardItem 1:1 Parent item β”‚
β”‚ β”‚
β”‚ NOTES β”‚
β”‚ ───── β”‚
β”‚ β”‚
β”‚ β€’ External storage for data > 100KB β”‚
β”‚ β€’ Compression using LZ4 for text > 10KB β”‚
β”‚ β€’ CloudKit: Stored as CKAsset β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Stores thumbnails and previews for quick display.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ ContentPreview β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key β”‚
β”‚ thumbnail Binary Image thumbnail (256px) β”‚
β”‚ textPreview String First 500 chars of text β”‚
β”‚ ocrText String? ⭐ OCR extracted text β”‚
β”‚ embedding Binary? ⭐ ML embedding vector β”‚
β”‚ β”‚
β”‚ RELATIONSHIPS β”‚
β”‚ ───────────── β”‚
β”‚ β”‚
β”‚ item ClipboardItem 1:1 Parent item β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Organizes clipboard items into folders (Smart Folders in Pro).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Folder β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key β”‚
β”‚ name String Folder name β”‚
β”‚ icon String SF Symbol name β”‚
β”‚ color String Hex color code β”‚
β”‚ sortOrder Int16 Display order β”‚
β”‚ isSmartFolder Bool ⭐ Auto-populated folder β”‚
β”‚ smartQuery String? ⭐ Query for smart folder β”‚
β”‚ β”‚
β”‚ RELATIONSHIPS β”‚
β”‚ ───────────── β”‚
β”‚ β”‚
β”‚ items [ClipboardItem] 1:N Items in folder β”‚
β”‚ parent Folder? N:1 Parent folder β”‚
β”‚ children [Folder] 1:N Subfolders β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Flexible tagging system for items.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Tag β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key β”‚
β”‚ name String Tag name (unique) β”‚
β”‚ color String Hex color code β”‚
β”‚ isAutoTag Bool System-generated tag β”‚
β”‚ β”‚
β”‚ RELATIONSHIPS β”‚
β”‚ ───────────── β”‚
β”‚ β”‚
β”‚ items [ClipboardItem] N:N Tagged items β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Apps excluded from clipboard monitoring.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Application β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ bundleId String App bundle identifier β”‚
β”‚ name String App display name β”‚
β”‚ isExcluded Bool Don't capture from this app β”‚
β”‚ excludeReason String? Why excluded β”‚
β”‚ β”‚
β”‚ Default Exclusions: β”‚
β”‚ β€’ 1Password (com.1password.1password) β”‚
β”‚ β€’ Bitwarden (com.bitwarden.desktop) β”‚
β”‚ β€’ LastPass (com.lastpass.LastPass) β”‚
β”‚ β€’ Keychain Access (com.apple.keychainaccess) β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Custom automation actions.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Action ⭐ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ id UUID Primary key β”‚
β”‚ name String Action name β”‚
β”‚ script String JavaScript code β”‚
β”‚ trigger String When to run (manual/auto) β”‚
β”‚ contentTypes [String] Applicable content types β”‚
β”‚ isEnabled Bool Action enabled β”‚
β”‚ β”‚
β”‚ Examples: β”‚
β”‚ β€’ "Uppercase Text" β”‚
β”‚ β€’ "Shorten URL" β”‚
β”‚ β€’ "Format JSON" β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

User settings stored in CoreData (synced with iCloud).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ UserPreference β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ATTRIBUTES β”‚
β”‚ ────────── β”‚
β”‚ β”‚
β”‚ key String Preference key β”‚
β”‚ value Transformable Codable value β”‚
β”‚ modifiedAt Date Last change β”‚
β”‚ β”‚
β”‚ Stored Preferences: β”‚
β”‚ β€’ historyLimit: Int β”‚
β”‚ β€’ theme: String β”‚
β”‚ β€’ globalHotkey: String β”‚
β”‚ β€’ launchAtLogin: Bool β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

CoreData EntityCloudKit Record TypeZone
ClipboardItemCD_ClipboardItemcom.pasteshelf.clipboard
ClipboardContentCD_ClipboardContent (CKAsset)com.pasteshelf.clipboard
ContentPreviewCD_ContentPreviewcom.pasteshelf.clipboard
FolderCD_Foldercom.pasteshelf.clipboard
TagCD_Tagcom.pasteshelf.clipboard
UserPreferenceCD_UserPreferencecom.pasteshelf.settings
// CloudKit Container Setup
let container = NSPersistentCloudKitContainer(name: "PasteShelf")
let cloudStoreDescription = NSPersistentStoreDescription()
cloudStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(
containerIdentifier: "iCloud.com.pasteshelf.PasteShelf"
)
// Custom zone for clipboard data
cloudStoreDescription.setOption(
true as NSNumber,
forKey: NSPersistentHistoryTrackingKey
)
cloudStoreDescription.setOption(
true as NSNumber,
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey
)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CloudKit Encryption β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Encryption Flow: β”‚
β”‚ β”‚
β”‚ 1. Generate device-specific key pair β”‚
β”‚ 2. Derive symmetric key from user's iCloud identity β”‚
β”‚ 3. Encrypt content before CoreData save β”‚
β”‚ 4. CoreData/CloudKit sync encrypted data β”‚
β”‚ 5. Decrypt on other devices with same iCloud account β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Plaintext │───▢│ AES-256 │───▢│ Ciphertext β”‚ β”‚
β”‚ β”‚ Content β”‚ β”‚ GCM β”‚ β”‚ (synced) β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ Key from Keychain β”‚
β”‚ (iCloud Keychain) β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

VersionDateChanges
1.02026-02-03Initial schema
// Lightweight migration (automatic)
let options = [
NSMigratePersistentStoresAutomaticallyOption: true,
NSInferMappingModelAutomaticallyOption: true
]
// Heavy migration (custom mapping model)
class MigrationManager {
func migrateStore(from sourceURL: URL, to destinationURL: URL) throws {
let sourceModel = // ...
let destinationModel = // ...
let mappingModel = NSMappingModel(
from: nil,
forSourceModel: sourceModel,
destinationModel: destinationModel
)
let migrationManager = NSMigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel
)
try migrationManager.migrateStore(
from: sourceURL,
sourceType: NSSQLiteStoreType,
options: nil,
with: mappingModel,
toDestinationURL: destinationURL,
destinationType: NSSQLiteStoreType,
destinationOptions: nil
)
}
}
  1. Prefer lightweight migrations when possible
  2. Add optional attributes with default values
  3. Never remove attributes in minor versions
  4. Version the data model for each schema change
  5. Test migrations with production-like data

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Index Configuration β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ ClipboardItem Indexes: β”‚
β”‚ β”‚
β”‚ 1. createdAt (DESC) - Default sort, recent first β”‚
β”‚ 2. textContent (FTS) - Full-text search β”‚
β”‚ 3. contentHash (UNIQUE) - Deduplication lookups β”‚
β”‚ 4. contentType - Filter by type β”‚
β”‚ 5. (isFavorite, createdAt) - Favorites query β”‚
β”‚ 6. (isPinned, createdAt) - Pinned items query β”‚
β”‚ 7. sourceApp - Filter by app β”‚
β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
// Batch fetching for large lists
let fetchRequest = ClipboardItem.fetchRequest()
fetchRequest.fetchBatchSize = 50
fetchRequest.fetchLimit = 100
fetchRequest.propertiesToFetch = ["id", "textContent", "createdAt", "contentType"]
fetchRequest.relationshipKeyPathsForPrefetching = ["preview"]
// Background fetch
let backgroundContext = container.newBackgroundContext()
backgroundContext.perform {
let items = try? backgroundContext.fetch(fetchRequest)
// Process items
}
Content TypeStorage Strategy
Text < 10KBInline in SQLite
Text β‰₯ 10KBCompressed (LZ4)
ImagesExternal file, thumbnail inline
FilesReference only, no duplication
Binary > 100KBExternal storage
class StorageManager {
func cleanup() async {
let context = container.newBackgroundContext()
await context.perform {
// Delete items older than retention period
let cutoffDate = Date().addingTimeInterval(-retentionPeriod)
let deleteRequest = NSBatchDeleteRequest(
fetchRequest: ClipboardItem.fetchRequest(
predicate: NSPredicate(format: "createdAt < %@ AND isFavorite == NO", cutoffDate as NSDate)
)
)
try? context.execute(deleteRequest)
// Compact database
try? context.persistentStoreCoordinator?.performAndWait {
// SQLite VACUUM
}
}
}
}

DocumentDescription
ArchitectureSystem architecture
Tech StackTechnology choices
Sync EngineCloudKit sync
PerformanceOptimization

Last updated: 2026-02-03