How OpenSEO Manages Workspace Merging and Organization Boundaries
OpenSEO handles workspace merging by detecting legacy delegated-<user-id> organizations and migrating all user data into a central shared-workspace, while enforcing organization boundaries through naming conventions, slug-based uniqueness, and authentication mode checks.
OpenSEO structures user data around organizations (also called workspaces). In Cloudflare Access deployments, each user originally received a private delegated organization. To simplify billing and collaboration, OpenSEO introduces a shared workspace and folds legacy per-user workspaces into it. The entire migration pipeline lives in the authentication layer according to the every-app/open-seo source code.
Legacy Workspace Detection and Counting
The merge process begins with identifying organizations that follow the legacy pattern. In src/server/auth/workspace-merge.ts, a SQL filter selects any organization whose ID matches delegated-%.
// Count legacy workspaces before merging
import { WorkspaceMergeService } from "@/server/auth/workspace-merge";
const { mergedWorkspaces } = await WorkspaceMergeService.countLegacyWorkspaces();
console.log(`Legacy workspaces pending merge: ${mergedWorkspaces}`);
The countLegacyWorkspaces function returns the total number of matching organizations, giving administrators visibility into the migration scope.
The Workspace Merging Process
mergeLegacyWorkspaces performs the actual data migration, but only when the system runs in cloudflare_access mode. This guard prevents accidental cross-mode data movement.
The function handles several data types:
- Projects — including conflicting "Default" projects that get renamed to preserve unique indexes
- Onboarding answers
- GSC/GA4 connections
- Activation timestamps — merged by taking the earliest timestamp across all organizations
// Execute the merge (cloudflare_access mode only)
import { WorkspaceMergeService } from "@/server/auth/workspace-merge";
try {
const result = await WorkspaceMergeService.mergeLegacyWorkspaces();
console.log(`Merged ${result.mergedWorkspaces} legacy workspaces into shared workspace.`);
} catch (e) {
console.error("Merge failed:", e);
}
After all rows are repointed to the shared workspace, the legacy organizations are permanently deleted.
Shared Workspace Definition and Creation
The shared workspace itself is defined in src/server/auth/delegated-organization.ts with a fixed ID of shared-workspace. This ID deliberately omits the delegated- prefix so the legacy detection filter excludes it.
// Ensure shared workspace exists on application startup
import { ensureSharedWorkspaceOrganization } from "@/server/auth/delegated-organization";
await ensureSharedWorkspaceOrganization(); // id = "shared-workspace"
The ensureSharedWorkspaceOrganization function upserts the organization record through AuthRepository, guaranteeing the shared workspace is available before any merge operations occur.
How Organization Boundaries Are Enforced
OpenSEO enforces workspace boundaries through three complementary mechanisms:
1. Naming Conventions
delegated-<user-id>— identifies legacy per-user workspaces subject to migrationshared-workspace— the unified destination that persists indefinitely
2. Slug Generation and Uniqueness
The ensureDelegatedOrganizationForUser function in src/server/auth/delegated-organization.ts constructs unique organization slugs by combining the email local-part with a hex-encoded user ID:
// Slug format: <email-local-part>-<hex-user-id>
// Example: alice-a1b2c3d4
This guarantees uniqueness while maintaining traceability to the originating user.
3. Authorization Mode Validation
The merge operation validates process.env.AUTH_MODE === 'cloudflare_access' before executing. Any other mode causes immediate abortion, protecting hosted deployments from unintended data reorganization.
Key Files and Responsibilities
| File | Role |
|---|---|
src/server/auth/workspace-merge.ts |
Core service for detecting, counting, and merging legacy organizations |
src/server/auth/delegated-organization.ts |
Shared workspace definition, creation, and delegated organization construction |
src/server/auth/repositories/AuthRepository.ts |
Database operations for organization records |
src/server/auth/org-slug.ts |
URL-safe slug generation and hex encoding utilities |
Summary
- Workspace merging in OpenSEO migrates data from
delegated-<user-id>organizations into a singleshared-workspace - Legacy detection uses SQL pattern matching on organization IDs in
workspace-merge.ts - Data integrity is preserved through conflict resolution for projects and timestamp merging for activations
- Boundaries are enforced via naming conventions, unique slug generation, and strict authentication mode checks
- The operation is irreversible — legacy organizations are deleted after successful migration
Frequently Asked Questions
What happens to conflicting "Default" projects during a merge?
Conflicting "Default" projects are automatically renamed before migration. This prevents unique index violations in the database while preserving all project data from legacy workspaces.
Can workspace merging run in hosted mode?
No. The mergeLegacyWorkspaces function explicitly checks for cloudflare_access mode and throws an error if the system is configured otherwise. This safety guard prevents data reorganization in single-tenant hosted deployments.
How does OpenSEO ensure the shared workspace always exists?
The ensureSharedWorkspaceOrganization function performs an upsert operation through AuthRepository whenever called. Application startup routines typically invoke this to guarantee the shared workspace is available before any user operations occur.
What activation data survives the merge?
Activation timestamps from all legacy organizations are compared, and the earliest timestamp is retained in the shared workspace. This preserves the true original activation date regardless of how many workspaces a user previously occupied.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →