SwiftUI Document APIs in 2027 Releases - Everything You Need to Know

August 8, 2026

SwiftUI’s document architecture has quietly become one of the most powerful foundations for building professional macOS, iPadOS, and iOS apps. Apps such as Xcode, Pages, Pixelmator Pro, and many creative tools rely on document-based workflows that provide autosaving, versioning, keyboard shortcuts, window management, and seamless integration with the file system.

In the 2027 releases, Apple significantly expands the SwiftUI document APIs with a more modern, performance-focused architecture.

The update introduces three major improvements:

  • DocumentCreationSource for contextual new-document creation flows.
  • Faster reading and writing for large documents using asynchronous readers, writers, snapshots, and background disk operations.
  • First-class direct document URL access through the modern FileDocument and ReferenceFileDocument document architecture.

These APIs make it easier to build document editors that feel native, remain responsive while handling large files, and provide richer creation experiences than the traditional “new blank document” flow.

In this article, we’ll explore the complete document architecture, build a real document-based app, and understand how the new APIs improve both performance and user experience.


Why document-based apps matter

A SwiftUI document app automatically gains a surprising amount of functionality.

When you use DocumentGroup, the system provides:

  • Command–N for creating documents.
  • Command–O for opening documents.
  • Automatic autosaving.
  • Edited state indicators.
  • Window restoration.
  • Document lifecycle management.
  • Integration with Files and Finder.
  • Multiwindow support.

The new APIs build on this foundation rather than replacing it.

The result is a cleaner separation between document data, disk operations, and user interface state.


The modern document architecture

A document-based app begins with DocumentGroup.

@main
struct SketchApp: App {
    var body: some Scene {
        DocumentGroup {
            SketchDocument()
        }

        WindowGroup {
            SettingsView()
        }
    }
}

DocumentGroup becomes the primary scene responsible for opening, creating, and managing documents.

Your document type becomes the central source of truth for the application.


A modern document model

The document architecture now works particularly well with the Observation framework.

import Observation

@Observable
final class SketchDocument {
    var canvasSize: CGSize = .init(width: 1024, height: 1024)
    var strokes: [Stroke] = []
    var backgroundImage: URL?
}

Using @Observable means SwiftUI updates only the views that actually depend on modified properties.

For large documents containing thousands of objects, this can significantly reduce unnecessary view updates.


Custom new-document flows with DocumentCreationSource

One of the most visible improvements is the ability to create documents from different starting points.

Consider a drawing app.

Previously, pressing New always created the same document.

Now you can offer multiple creation experiences:

  • Blank canvas
  • Photo-based canvas
  • Template
  • Imported asset
  • Scanned document

The new DocumentCreationSource API makes this straightforward.

Defining creation sources

enum SketchCreationSource: String, Codable {
    case blank
    case photo
    case template
}

Creating launch buttons

DocumentGroup(
    newDocument: SketchDocument.init
) {
    EditorView(document: $0.document)
} launchScene: {

    NewDocumentButton("Blank Canvas")

    NewDocumentButton(
        "From Photo",
        source: SketchCreationSource.photo
    )

    NewDocumentButton(
        "Template",
        source: SketchCreationSource.template
    )
}

Each button declares a different document creation source.

When the user selects a button, SwiftUI passes the selected source into the document creation context.


Reading the creation context

The document initializer receives contextual information about how the document was created.

init(context: DocumentCreationContext) {

    if let source = context.creationSource(
        SketchCreationSource.self
    ) {

        switch source {
        case .blank:
            break

        case .photo:
            showingPhotoPicker = true

        case .template:
            loadDefaultTemplate()
        }
    }
}

This enables experiences that previously required custom launch windows and complicated routing logic.

For example, selecting From Photo can immediately present the photo picker.

The user is only one tap away from editing a photo.

This creates a much more polished onboarding experience.


Separating reading and writing

Earlier document APIs tended to combine multiple responsibilities into a single type.

The new architecture separates them into distinct protocols.

Responsibility Protocol
Reading documents ReadableDocument
Writing documents WritableDocument
Disk reading implementation DocumentReader
Disk writing implementation DocumentWriter

This separation improves testability, concurrency, and performance.


Writing documents efficiently

Let’s make our document writable.

import UniformTypeIdentifiers

extension SketchDocument: WritableDocument {

    static let writableContentTypes: [UTType] = [
        .sketchDocument
    ]
}

The protocol requires three main pieces:

  • Supported content types.
  • A snapshot.
  • A document writer.

Understanding snapshots

The snapshot is one of the most important architectural changes.

Instead of writing the live document directly to disk, you create an immutable representation of the document.

struct SketchSnapshot: Sendable {
    var canvasSize: CGSize
    var strokes: [Stroke]
    var backgroundImage: URL?
}

The document provides a snapshot.

@MainActor
func snapshot(contentType: UTType)
async throws -> sending SketchSnapshot {

    SketchSnapshot(
        canvasSize: canvasSize,
        strokes: strokes,
        backgroundImage: backgroundImage
    )
}

This captures the document at a single point in time.

The writer can safely use this snapshot in the background.


Creating a document writer

The writer handles all disk operations.

struct SketchWriter: DocumentWriter {

    typealias Snapshot = SketchSnapshot

    let contentType: UTType
}

The core method is write.

nonisolated
func write(
    snapshot: sending SketchSnapshot,
    to destination: URL,
    previous: sending SketchSnapshot?,
    progress: consuming Subprogress
) async throws {

    // Write document package
}

Several details here are worth discussing.


Why nonisolated matters

The write method is nonisolated.

This means it is not bound to the main actor.

Expensive work such as:

  • image encoding
  • JSON serialization
  • ZIP creation
  • package generation
  • file copying

can occur in the background.

The UI remains responsive while saving.

For large creative documents, this is a major improvement.


Incremental document writing

Notice the previous snapshot parameter.

previous: sending SketchSnapshot?

This allows incremental saving.

Instead of rewriting the entire document package every time, you can compare snapshots.

if snapshot.backgroundImage != previous?.backgroundImage {
    writeBackgroundImage()
}

if snapshot.strokes != previous?.strokes {
    writeStrokeData()
}

For package-based formats, this can dramatically reduce disk activity.


Reporting save progress

Large saves should provide feedback.

SwiftUI integrates with Foundation’s progress reporting.

progress.complete(count: 1, of: 3)
writeMetadata()

progress.complete(count: 2, of: 3)
writeImages()

progress.complete(count: 3, of: 3)
writeThumbnail()

This allows the system to display accurate save progress when needed.


Reading documents

Reading follows the same architecture.

extension SketchDocument: ReadableDocument {

    static let readableContentTypes: [UTType] = [
        .sketchDocument
    ]
}

A document reader performs the heavy disk work.

The document then applies the loaded snapshot.

Conceptually, the flow becomes:

Disk
 ↓
DocumentReader
 ↓
Snapshot
 ↓
SketchDocument
 ↓
SwiftUI Views

This keeps disk operations isolated from UI state.


Supporting multiple export formats

One of the most powerful aspects of WritableDocument is support for multiple output formats.

Suppose the app can export both its native document format and PNG images.

static let writableContentTypes: [UTType] = [
    .sketchDocument,
    .png
]

The writer can branch based on the requested content type.

nonisolated
func write(
    snapshot: sending SketchSnapshot,
    to destination: URL,
    previous: sending SketchSnapshot?,
    progress: consuming Subprogress
) async throws {

    if contentType.conforms(to: .sketchDocument) {
        writeNativeDocument(snapshot)
    }
    else if contentType.conforms(to: .png) {
        writePNG(snapshot)
    }
}

The PNG export can render the canvas using Core Graphics.

let context = CGContext(...)

drawBackground(snapshot, in: context)
drawStrokes(snapshot, in: context)

context.writePNG(to: destination)

The same document can now support:

  • native editing
  • flattened image export
  • sharing
  • printing
  • cloud integration

without separate export controllers.


Performance improvements for large documents

These APIs are particularly valuable for large documents.

Imagine a document containing:

  • 50,000 vector objects
  • multiple high-resolution images
  • embedded metadata
  • generated thumbnails

The old approach often involved:

  • main-thread serialization
  • full document rewrites
  • coarse view invalidation

The new architecture enables:

Background serialization

The writer runs asynchronously.

Immutable snapshots

Serialization never races with live editing.

Incremental updates

Only changed resources need rewriting.

Fine-grained observation

Views update only when accessed properties change.

The combination substantially improves responsiveness.


Direct document URL access

Another important addition is first-class direct document URL access.

Document-based apps often need the document’s actual location.

Common scenarios include:

  • loading external resources
  • creating relative file references
  • package manipulation
  • security-scoped bookmarks
  • collaboration metadata
  • integration with other tools

The expanded document APIs provide direct access to the document URL through the document architecture.

This removes much of the bookkeeping that previously required environment objects, window state tracking, or custom file coordination.

For package-based document formats, direct URL access makes it much easier to work with internal resources.


Choosing FileDocument vs ReferenceFileDocument

SwiftUI continues to support both document styles.

FileDocument

Use value semantics.

Best for:

  • text documents
  • JSON documents
  • configuration files
  • small to medium files

Example:

struct NotesDocument: FileDocument {
    var text: String
}

ReferenceFileDocument

Use reference semantics.

Best for:

  • large documents
  • media projects
  • drawing applications
  • design tools
  • package-based formats

Example:

@Observable
final class SketchDocument: ReferenceFileDocument {
    var strokes: [Stroke] = []
}

For most modern creative apps, ReferenceFileDocument is usually the better fit.


A complete document workflow

The new architecture creates a remarkably clean document lifecycle.

New Document Button
        ↓
DocumentCreationSource
        ↓
DocumentCreationContext
        ↓
SketchDocument
        ↓
Observable state
        ↓
SwiftUI views
        ↓
Snapshot
        ↓
DocumentWriter
        ↓
Disk

Reading follows the reverse direction.

The document remains focused on application state.

Readers and writers focus on file system operations.

Snapshots provide safe concurrency boundaries.


Migration from older document APIs

For existing document-based apps, migration can be incremental.

Start by adopting @Observable.

Then separate reading and writing logic.

Introduce snapshots.

Finally, move expensive serialization into DocumentWriter.

A practical migration path is:

  1. Convert the document model to @Observable.
  2. Create a snapshot type.
  3. Move writing code into DocumentWriter.
  4. Move reading code into DocumentReader.
  5. Add DocumentCreationSource for richer creation flows.
  6. Add additional export formats.

Each step provides independent benefits.


Final thoughts

The expanded SwiftUI document APIs are much more than a small refinement.

They modernize the entire document architecture around Observation, Swift Concurrency, and asynchronous disk operations.

The biggest improvement is the clear separation between document state and file system work.

DocumentCreationSource enables creation experiences that feel much more native and intentional.

ReadableDocument and WritableDocument make large-file performance significantly better through snapshots and background readers and writers.

Direct document URL access simplifies integration with the broader file system and package-based document formats.

If you’re building a professional editor, drawing app, writing app, or any tool that works with files, these APIs make SwiftUI document-based apps substantially more capable, scalable, and maintainable than previous releases.

Thank you for reading. If you have any questions or opinion feel free to follow me on X and send me a DM. If this article helped you, Buy me a coffee.