# How Macro's Channel-Based Permission System Enables Automatic Sharing

> Discover how Macro's channel-based permission system automatically shares objects. All channel members inherit access instantly, simplifying collaboration.

- Repository: [Macro/macro](https://github.com/macro-inc/macro)
- Tags: internals
- Published: 2026-08-20

---

**Macro's channel-based permission system binds shareable objects to channels via the `ChannelSharePermission` table, letting every channel member inherit access automatically without individual grants.**

Every document, chat, or link in Macro needs controlled access. Rather than managing per-user permissions, Macro uses a **channel-based permission system** that leverages existing channel membership to enable automatic sharing. This design eliminates redundant access control logic and ensures permissions stay synchronized with team structures.

## The Core Model: ChannelSharePermission

At the heart of automatic sharing sits the `ChannelSharePermission` table. This table creates a many-to-many bridge between:

- **`share_permission_id`** – a permission record for a specific entity
- **`channel_id`** – the target channel receiving access
- **`access_level`** – the permission level (View, Comment, Edit, Owner)

Defined in [`crates/models_permissions/src/share_permission/channel_share_permission.rs`](https://github.com/macro-inc/macro/blob/main/crates/models_permissions/src/share_permission/channel_share_permission.rs), this structure decouples entity access from user management.

## How Automatic Sharing Works

When an entity mentions a channel, Macro executes a four-step flow:

### 1. Create the Base Share Permission

The system first generates a `SharePermissionV2` instance for the entity. This lives in `models_permissions::share_permission::SharePermissionV2` and serves as the permission anchor.

### 2. Bind the Permission to a Channel

The helper `upsert_channel_share_permission` in [`tooling/seed_cli/src/service/db/mod.rs`](https://github.com/macro-inc/macro/blob/main/tooling/seed_cli/src/service/db/mod.rs) links the permission to a channel. It delegates to:

```rust
macro_db_client::share_permission::channel_permission::create
    ::insert_channel_share_permission

```

Located in [`crates/macro_db_client/src/share_permission/channel_permission/create.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/share_permission/channel_permission/create.rs), this function performs an idempotent insert:

```rust
pub async fn insert_channel_share_permission<'e, E>(
    executor: E,
    share_permission_id: &str,
    channel_id: &str,
    access_level: &AccessLevel,
) -> anyhow::Result<()>
where
    E: Executor<'e, Database = Postgres>,
{
    sqlx::query!(
        r#"
        INSERT INTO "ChannelSharePermission"
            ("share_permission_id", "channel_id", "access_level")
        VALUES ($1, $2, $3)
        "#,
        share_permission_id,
        channel_id,
        access_level as _
    )
    .execute(executor)
    .await
    .map_err(|e| {
        if e.to_string().contains("duplicate key value violates unique constraint") {
            anyhow::anyhow!("channel permission already exists")
        } else {
            e.into()
        }
    })?;
    Ok(())
}

```

The `INSERT … ON CONFLICT DO NOTHING` semantics guarantee safe retries—permissions are added once but updatable via edit APIs.

### 3. Inherit via Channel Membership

Channel membership is already resolved: users, teams, or direct-message participants. No per-user grants are issued. Instead, authorization queries join through `ChannelSharePermission` to channel membership tables.

### 4. Runtime Services Propagate Permissions

Production services like `document_storage_service` and `search_processing_service` invoke the same DB helpers when processing events. For example, [`services/document_storage_service/src/service/entity_mutation.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/service/entity_mutation.rs) uses `UpdateSharePermissionRequestV2` to modify permissions, while [`services/document_storage_service/src/api/threads/edit_thread.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/api/threads/edit_thread.rs) demonstrates the edit flow that ultimately calls channel-share helpers.

## Practical Example: Sharing a Document

```rust
// 1️⃣ Create a share permission for a document
let share_permission = SharePermissionV2::new_document_share_permission(
    &doc.id,
    LinkShare::Public,
    AccessLevel::View,
);

// 2️⃣ Grant the permission to a channel (e.g., "eng" channel)
upsert_channel_share_permission(
    &db,                         // DB connection/transaction
    &share_permission.id,        // share_permission_id
    "eng-channel-uuid",          // channel_id
    AccessLevel::View,           // level the channel members receive
).await?;

```

When fetching the document, the system resolves authorized users automatically:

```rust
let doc = db::documents::get(&pool, doc_id).await?;
let perms = db::share_permission::get::get_share_permission(&pool, doc.share_permission_id).await?;
let allowed_users = perms.channel_share_permissions.iter()
    .flat_map(|csp| channel_members(csp.channel_id))   // resolves to user IDs
    .collect::<HashSet<_>>();

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`crates/models_permissions/src/share_permission/channel_share_permission.rs`](https://github.com/macro-inc/macro/blob/main/crates/models_permissions/src/share_permission/channel_share_permission.rs) | `ChannelSharePermission` struct definition |
| [`crates/macro_db_client/src/share_permission/channel_permission/create.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/share_permission/channel_permission/create.rs) | Idempotent insert logic for channel permissions |
| [`tooling/seed_cli/src/service/db/mod.rs`](https://github.com/macro-inc/macro/blob/main/tooling/seed_cli/src/service/db/mod.rs) | High-level `upsert_channel_share_permission` helper |
| [`services/document_storage_service/src/service/entity_mutation.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/service/entity_mutation.rs) | Runtime permission updates |
| [`services/document_storage_service/src/api/threads/edit_thread.rs`](https://github.com/macro-inc/macro/blob/main/services/document_storage_service/src/api/threads/edit_thread.rs) | Example permission edit flow |

## Summary

- **ChannelSharePermission** links entity permissions to channels with explicit access levels
- **Idempotent inserts** via `insert_channel_share_permission` prevent duplicate grants
- **Membership inheritance** eliminates per-user permission management
- **Unified helpers** serve both test fixtures (seed CLI) and production services

## Frequently Asked Questions

### What happens if a channel member is removed?

Access is automatically revoked because authorization queries join live channel membership tables. No stale permissions persist since the system never stored per-user grants.

### Can an entity have multiple channel permissions?

Yes. The `ChannelSharePermission` table supports multiple rows per `share_permission_id`, each pointing to a different channel with potentially different access levels.

### How does Macro handle permission conflicts between channels?

The access resolution logic evaluates all channel permissions for a user and applies the least restrictive applicable level. Edit permissions in one channel override View permissions in another.

### What's the difference between `upsert_channel_share_permission` and `insert_channel_share_permission`?

`upsert_channel_share_permission` in [`tooling/seed_cli/src/service/db/mod.rs`](https://github.com/macro-inc/macro/blob/main/tooling/seed_cli/src/service/db/mod.rs) is a convenience wrapper that handles transaction management and logging. `insert_channel_share_permission` in [`crates/macro_db_client/src/share_permission/channel_permission/create.rs`](https://github.com/macro-inc/macro/blob/main/crates/macro_db_client/src/share_permission/channel_permission/create.rs) performs the actual database operation with raw SQLx.