Skip to main content
Tuist workspaces help you organize multiple projects into a cohesive development environment, enabling modular architecture and efficient team collaboration.

Understanding Workspaces

An Xcode workspace is a container that groups multiple projects, allowing them to work together while maintaining independence.

Without Workspace

Single project, all targets in one .xcodeproj

With Workspace

Multiple projects organized in one .xcworkspace

When to Use Workspaces

Use a single Project.swift when:
  • You have a simple app structure
  • All targets are closely related
  • Team is small (< 5 developers)
  • Build times are acceptable
/MyApp
└── Project.swift
Tuist automatically creates a workspace for single projects - no Workspace.swift needed!

Creating a Workspace

Define a workspace in Workspace.swift:
import ProjectDescription

let workspace = Workspace(
    name: "MyWorkspace",
    projects: [
        "App",
        "Framework",
        "Core"
    ]
)

Workspace Structure Patterns

Organize by product features:
/MyWorkspace
├── Workspace.swift
├── App/
   └── Project.swift           # Main app
├── Features/
   ├── Authentication/
   └── Project.swift       # Auth feature
   ├── Profile/
   └── Project.swift       # Profile feature
   └── Settings/
       └── Project.swift       # Settings feature
└── Core/
    └── Project.swift           # Shared utilities
Workspace.swift
let workspace = Workspace(
    name: "MyApp",
    projects: [
        "App",
        "Features/**",
        "Core"
    ]
)

Cross-Project Dependencies

Link targets across projects in the workspace:
import ProjectDescription

let project = Project(
    name: "App",
    targets: [
        .target(
            name: "App",
            destinations: .iOS,
            product: .app,
            bundleId: "com.example.app",
            sources: ["Sources/**"],
            dependencies: [
                // Reference targets from other projects
                .project(target: "Profile", path: "../Features/Profile"),
                .project(target: "Settings", path: "../Features/Settings"),
                .project(target: "Core", path: "../Core")
            ]
        )
    ]
)
Avoid circular dependencies between projects. Tuist will detect and report these during generation.

Workspace Schemes

Create schemes that span multiple projects:
Workspace.swift
import ProjectDescription

let workspace = Workspace(
    name: "MyWorkspace",
    projects: ["App", "Features/**", "Core"],
    schemes: [
        // Build all features
        .scheme(
            name: "AllFeatures",
            shared: true,
            buildAction: .buildAction(
                targets: [
                    .project(path: "Features/Profile", target: "Profile"),
                    .project(path: "Features/Settings", target: "Settings"),
                    .project(path: "Features/Analytics", target: "Analytics")
                ]
            ),
            testAction: .targets(
                [
                    .testableTarget(target: .project(path: "Features/Profile", target: "ProfileTests")),
                    .testableTarget(target: .project(path: "Features/Settings", target: "SettingsTests"))
                ],
                options: .options(coverage: true)
            )
        ),
        
        // Integration tests
        .scheme(
            name: "Integration",
            shared: true,
            buildAction: .buildAction(
                targets: [
                    .project(path: "App", target: "App")
                ]
            ),
            testAction: .targets(
                [
                    .testableTarget(target: .project(path: "App", target: "IntegrationTests"))
                ]
            )
        ),
        
        // CI scheme
        .scheme(
            name: "CI",
            shared: true,
            buildAction: .buildAction(
                targets: [
                    .project(path: "App", target: "App"),
                    .project(path: "Core", target: "Core")
                ]
            ),
            testAction: .targets(
                [
                    .testableTarget(target: .project(path: "App", target: "AppTests")),
                    .testableTarget(target: .project(path: "Core", target: "CoreTests"))
                ],
                options: .options(
                    coverage: true,
                    codeCoverageTargets: [
                        .project(path: "App", target: "App"),
                        .project(path: "Core", target: "Core")
                    ]
                )
            )
        )
    ]
)

Additional Files

Include documentation and configuration files in the workspace:
additionalFiles: [
    // Documentation
    "README.md",
    "Documentation/**",
    
    // Configuration
    ".swiftlint.yml",
    ".swiftformat",
    
    // CI/CD
    ".github/**",
    
    // Folder references (blue folders)
    .folderReference(path: "Design")
]
Additional files appear in Xcode’s project navigator but aren’t built - perfect for documentation!

Workspace Generation Options

Customize workspace behavior:
generationOptions: .options(
    // Disable auto-generated workspace schemes
    autogeneratedWorkspaceSchemes: .disabled,
    
    // Enable synthetic projects for Swift packages
    renderMarkdownReadme: true
)

Best Practices

Ensure dependencies flow in one direction:
App → Features → Core
     ↘       ↗
      Services
Avoid:
App ⇄ Features  ❌  (circular dependency)
Use protocols/interfaces to invert dependencies when needed:
// In Core
protocol AnalyticsService {}

// In Features/Analytics
class FirebaseAnalytics: AnalyticsService {}

// In App
let analytics: AnalyticsService = FirebaseAnalytics()
Create team-specific workspaces:
# Mobile team workspace
/Mobile/Workspace.swift

# Backend team workspace  
/Backend/Workspace.swift

# Shared infrastructure
/Shared/Workspace.swift
Structure projects to minimize rebuilds:
  1. Small, focused projects - Changes affect fewer targets
  2. Static frameworks - Faster linking than dynamic
  3. Explicit dependencies - Avoid unnecessary rebuilds
.target(
    name: "Core",
    product: .staticFramework,  // Fast linking
    dependencies: []             // Minimal dependencies
)
Include architecture documentation in the workspace:
additionalFiles: [
    "Architecture.md",
    "Documentation/ModuleGuide.md",
    .folderReference(path: "Documentation")
]

Visualizing Your Workspace

Tuist can generate a visual graph of your workspace:
# Generate dependency graph
tuist graph

# Export as image
tuist graph --format png

# Skip external dependencies
tuist graph --skip-external-dependencies
Example output:

Migration Strategies

Gradually extract features from a monolithic app:
1

Start with Core

Extract shared utilities first:
/MyApp
├── App/Project.swift         # Main app (monolith)
└── Core/Project.swift        # Extracted utilities
2

Extract Features

Move features one at a time:
/MyApp
├── Workspace.swift
├── App/Project.swift
├── Features/
   └── Profile/Project.swift  # First extracted feature
└── Core/Project.swift
3

Continue Extraction

Gradually move all features:
/MyApp
├── Workspace.swift
├── App/Project.swift         # Now just app shell
├── Features/
   ├── Profile/Project.swift
   ├── Settings/Project.swift
   └── Analytics/Project.swift
└── Core/Project.swift

Example: Real-World Workspace

Here’s a complete example of a production workspace:
import ProjectDescription

let workspace = Workspace(
    name: "ShopApp",
    projects: [
        "App",
        "Features/**",
        "Services/**",
        "Core"
    ],
    schemes: [
        .scheme(
            name: "ShopApp-Development",
            shared: true,
            buildAction: .buildAction(
                targets: [.project(path: "App", target: "ShopApp")]
            ),
            testAction: .targets(
                [
                    .testableTarget(target: .project(path: "App", target: "ShopAppTests")),
                    .testableTarget(target: .project(path: "Features/Catalog", target: "CatalogTests"))
                ],
                options: .options(coverage: true)
            ),
            runAction: .runAction(
                executable: .project(path: "App", target: "ShopApp"),
                arguments: .arguments(
                    environmentVariables: [
                        "API_URL": "https://dev-api.shop.com"
                    ]
                )
            )
        )
    ],
    additionalFiles: [
        "README.md",
        "Documentation/**"
    ]
)

Next Steps

Manifests

Deep dive into manifest file structure

Generated Projects

Understand project generation

Architecture

Learn about Tuist’s architecture

Dependencies

Managing external dependencies

Build docs developers (and LLMs) love