Skip to main content
Extensions in Spacedrive are built with Rust macros that generate the integration code for you. Define your data models, jobs, and agents using attributes. The SDK handles sync, permissions, and sandboxing automatically. This page covers the five building blocks: extensions, models, jobs, agents, and actions. Each concept maps to a macro that generates the boilerplate.

1. Extension Definition (#[extension])

The #[extension] attribute is the entry point for your extension. It defines metadata, dependencies, and the permissions your extension requires.
#[extension(
    id = "com.spacedrive.photos",  // Unique reverse-DNS ID
    name = "Photos Manager",
    version = "1.0.0",
    description = "Advanced photo organization with faces and places.",
    min_core_version = "2.0.0",  // Minimum Spacedrive core version
    permissions = [
        Permission::ReadEntries(glob = "**/*.{jpg,png,heic}"),
        Permission::WriteTags,
    ]
)]
struct Photos {
    config: PhotosConfig,  // User-configurable settings
}
  • Permissions: Your extension requests permissions, and the user grants them, often scoped to specific parts of their library for privacy.
  • Config: You can define a configuration struct that allows users to customize your extension’s behavior.

2. Models (#[model])

Models are the heart of your extension’s data structure. The #[model] attribute allows you to define custom data models that are stored in real database tables, not as JSON blobs. This enables powerful querying, indexing, and relationships.
#[model(
    table_name = "person",
    version = "1.0.0",
    sync_strategy = "shared"
)]
#[index("email")]
struct Person {
    #[primary_key]
    id: Uuid,
    name: String,
    #[indexed]
    email: Option<String>,
    #[metadata]
    metadata_id: i32, // Enables tagging
}
  • Real Tables: Your models become actual SQL tables, prefixed with your extension’s ID (e.g., ext_photos_person).
  • Relationships: You can define foreign key relationships to core Spacedrive objects or other models in your extension.
  • Tagging: By including a #[metadata] field, your models can be tagged and organized just like regular files.

3. Jobs and Tasks (#[job], #[task])

Jobs and tasks are used to perform long-running or background operations, such as analyzing files or processing data. They are durable, resumable, and can be run in parallel.
#[task(retries = 3, timeout_ms = 30000)]
async fn detect_faces(ctx: &TaskContext, entry: &Entry) -> TaskResult<Vec<FaceDetection>> {
    // ... face detection logic ...
}

#[job(parallelism = 4, trigger = "user_initiated")]
async fn analyze_photos(ctx: &JobContext, location: SdPath) -> JobResult<()> {
    // ... logic to find photos and run detect_faces task ...
}
  • Durability: Jobs are persisted and can survive application restarts.
  • Triggers: Jobs can be initiated by users, events, or other system triggers.

4. AI Agents (#[agent])

Agents provide a way to create autonomous logic within your extension. They can observe events, reason about data, and act by dispatching jobs or modifying data.
#[agent]
impl Photos {
    async fn on_new_photo(&self, ctx: &AgentContext, photo: Photo) -> AgentResult<()> {
        ctx.trace("New photo added - checking for faces");
        // ... logic to analyze photo ...
        Ok(())
    }
}
  • Observe-Orient-Act: Agents follow a classic AI loop to respond to changes in the system.
  • Memory: Agents can have their own memory to store knowledge and plans.

5. Actions (#[action])

Actions are user-invokable operations that can be previewed before execution. This ensures that users always know what changes an extension is about to make.
#[action]
async fn organize_photos(ctx: &ActionContext, location: SdPath) -> ActionResult<ActionPreview> {
    // Works with any storage type
    match location {
        SdPath::Physical { .. } => organize_local_photos(ctx, location).await,
        SdPath::Cloud { .. } => organize_cloud_photos(ctx, location).await,
        SdPath::Content { .. } => {
            // Resolve content path to physical location first
            let resolved = location.resolve(ctx).await?;
            organize_photos(ctx, resolved).await
        }
    }
}

#[action_execute]
async fn organize_photos_execute(ctx: &ActionContext, preview: ActionPreview) -> ActionResult<()> {
    // ... apply the changes ...
}
  • Safety: The preview-commit-verify flow ensures that all operations are safe and predictable.
  • Universal: Actions work seamlessly across local, cloud, and content-addressed files.