PHPackages                             inmanturbo/homework-organizations - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. inmanturbo/homework-organizations

ActiveLibrary[Database &amp; ORM](/categories/database)

inmanturbo/homework-organizations
=================================

Database-per-organization isolation for Homework OAuth client applications

v0.0.1(7mo ago)00MITPHPPHP ^8.2CI passing

Since Oct 8Pushed 7mo agoCompare

[ Source](https://github.com/inmanturbo/homework-organizations)[ Packagist](https://packagist.org/packages/inmanturbo/homework-organizations)[ RSS](/packages/inmanturbo-homework-organizations/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (1)Dependencies (10)Versions (2)Used By (0)

Homework Organizations
======================

[](#homework-organizations)

[![Latest Version on Packagist](https://camo.githubusercontent.com/68af7285e60ff05aef8526725a5429510c693d12391e0a7ecdcfeb53d73e5105/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e6d616e747572626f2f686f6d65776f726b2d6f7267616e697a6174696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/inmanturbo/homework-organizations)[![Tests](https://camo.githubusercontent.com/a1d2de080fae67dcd76a00e1184201df640a14c1eef07085b378e8c8b40a228b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f696e6d616e747572626f2f686f6d65776f726b2d6f7267616e697a6174696f6e732f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/inmanturbo/homework-organizations/actions/workflows/tests.yml)[![Code Style](https://camo.githubusercontent.com/c8a21fbe18c8984d4687bd05d5b7902591192d10b5681b283598b9ba638f348b/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f696e6d616e747572626f2f686f6d65776f726b2d6f7267616e697a6174696f6e732f70696e742e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/inmanturbo/homework-organizations/actions/workflows/pint.yml)[![Total Downloads](https://camo.githubusercontent.com/834d7273f0e4e98b49082b05657edfcb08c1057e6369eb3e55a5d6dcc99a5a1f/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f696e6d616e747572626f2f686f6d65776f726b2d6f7267616e697a6174696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/inmanturbo/homework-organizations)

Database-per-organization support for Laravel applications using the Homework OAuth ecosystem. Provides database-per-organization isolation with zero code changes required in your application logic.

Table of Contents
-----------------

[](#table-of-contents)

- [Related Packages](#related-packages)
- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [WorkOS Integration](#workos-integration)
- [How It Works](#how-it-works)
- [Configuration](#configuration)
- [Advanced Usage](#advanced-usage)
- [Architecture](#architecture)
- [Roadmap](#roadmap)
- [Testing](#testing)
- [License](#license)

Related Packages
----------------

[](#related-packages)

**[Homework](https://github.com/inmanturbo/homework)** - Laravel Passport extension providing WorkOS UserManagement API compatibility. Self-host your authentication with full WorkOS SDK compatibility. Use together with Homework Organizations for a complete multi-organization SaaS authentication and data isolation solution.

Features
--------

[](#features)

- **Database-per-Organization** - Automatic organization database switching based on authenticated user
- **Invisible Organization Isolation** - No code changes needed - organization isolation happens automatically
- **Storage Isolation** - Organization-specific storage folders while maintaining standard Laravel `storage:link` compatibility
- **Queue Organization Context** - Jobs automatically execute in correct organization context using Laravel Context
- **Context Integration** - Organization information automatically available in logs and across requests/jobs
- **Pipeline Architecture** - Extensible actions for custom organization switching logic
- **Auto-Migration** - Organization databases created and migrated automatically on first access
- **Event-Driven** - `OrganizationSwitching` and `OrganizationSwitched` events for hooks
- **Octane-Safe** - Scoped singleton bindings prevent organization bleeding

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11.x
- SQLite extension (for central database)

Installation
------------

[](#installation)

### Step 1: Install the Package

[](#step-1-install-the-package)

```
composer require inmanturbo/homework-organizations
```

### Step 2: Run the Install Command

[](#step-2-run-the-install-command)

```
php artisan organizations:install
```

This command will:

- Configure `.env` to use SQLite for sessions, cache, and queue
- Comment out `DB_DATABASE` in `.env` (uses default `database/database.sqlite`)
- Modify User model to use `workos_id` as primary key
- Update users table migration to use `workos_id` as primary key

### Step 3: Run Migrations

[](#step-3-run-migrations)

Run SQLite migrations for infrastructure (sessions, cache, jobs):

```
php artisan migrate --database=sqlite \
  --path="database/migrations/0001_01_01_000000_create_users_table.php" \
  --path="database/migrations/0001_01_01_000001_create_cache_table.php" \
  --path="database/migrations/0001_01_01_000002_create_jobs_table.php"
```

**Organization databases are created and migrated automatically** when a user first accesses their organization (via the `auto_migrate` config option, enabled by default). No manual migration needed!

WorkOS Integration
------------------

[](#workos-integration)

When using this package with Laravel WorkOS authentication, use the `OrganizationUserResolver` to handle organization-aware user operations:

```
use Inmanturbo\HomeworkOrganizations\Support\OrganizationUserResolver;
use Laravel\WorkOS\Http\Requests\AuthKitAuthenticationRequest;

Route::get('authenticate', function (AuthKitAuthenticationRequest $request) {
    return tap(to_route('dashboard'), fn () => $request->authenticate(
        findUsing: OrganizationUserResolver::findUsing(...),
        createUsing: OrganizationUserResolver::createUsing(...),
        updateUsing: OrganizationUserResolver::updateUsing(...),
    ));
})->middleware(['guest']);
```

The `OrganizationUserResolver` automatically:

- Switches to the correct organization database before finding/creating/updating users
- Adds `organization_id` to Laravel Context for request tracking
- Stores `organization_id` in session for subsequent requests
- Ensures users are created in the correct organization database
- Updates the `organization_id` field when user data changes

### Switching Organizations

[](#switching-organizations)

Users can switch to a different organization by visiting the `/select-organization` route. This will:

1. Clear the current organization from session
2. Log the user out
3. Redirect to login, triggering organization selection on the OAuth server

Add a link in your navigation:

```
Switch Organization
```

This route is automatically registered by the package.

How It Works
------------

[](#how-it-works)

### Database Strategy

[](#database-strategy)

**SQLite Database** (`database/database.sqlite`): Stores infrastructure data shared across all organizations:

- Sessions
- Cache
- Queue jobs
- User records (with `organization_id` linking to organizations)

**Organization Databases** (MySQL/dynamic): Isolated per organization using convention-based naming:

- Database name format: `{default_database}_{organization_id}`
- Example: `my_app_org_123`
- Contains all application data for that organization
- Uses your default database connection (MySQL, PostgreSQL, etc.)

### Automatic Organization Switching

[](#automatic-organization-switching)

The package automatically switches organization context based on:

**Web Requests:**

```
// Middleware checks authenticated user's organization_id
// Automatically switches to correct organization database
```

**Queue Jobs:**

```
// Laravel Context automatically propagates organization information
// Job executes in correct organization context
// No changes needed to job classes
```

### Context Integration

[](#context-integration)

Organization information is automatically added to Laravel's Context and available throughout your application:

```
use Illuminate\Support\Facades\Context;

// Available context data:
Context::get('tenant.organization_id'); // Current organization ID
Context::get('tenant.user_id');        // Current user ID
Context::get('tenant.database');       // Current organization database name
Context::get('tenant.storage.public'); // Public storage path
Context::get('tenant.storage.private'); // Private storage path
```

This context is:

- Automatically propagated to queued jobs
- Included in log entries for better debugging
- Shared across HTTP requests and commands
- Available in event listeners and middleware

### Storage Isolation

[](#storage-isolation)

Organization-specific storage paths are automatically configured:

**Public Storage:**

```
storage/app/public/organization_org_123/

```

Accessible via standard `storage:link`: `https://example.com/storage/organization_org_123/file.jpg`

**Private Storage:**

```
storage/app/private/organization_org_123/

```

Use Laravel's storage facade as normal - organization isolation is transparent:

```
// Automatically uses organization-specific path
Storage::disk('public')->put('avatars/user.jpg', $file);
Storage::disk('private')->put('documents/invoice.pdf', $pdf);
```

Configuration
-------------

[](#configuration)

Publish the configuration file (optional):

```
php artisan vendor:publish --tag=organizations-config
```

### Available Options

[](#available-options)

**`config/organizations.php`:**

```
return [
    // Pipeline actions executed when switching organizations
    'actions' => [
        \Inmanturbo\HomeworkOrganizations\Actions\SwitchOrganizationDatabase::class,
        \Inmanturbo\HomeworkOrganizations\Actions\SwitchOrganizationStorage::class,
        // Add your custom actions here
    ],

    // How long to cache organization database names (seconds)
    'cache_ttl' => env('ORGANIZATIONS_CACHE_TTL', 3600),

    // Auto-migrate organization databases on first access
    'auto_migrate' => env('ORGANIZATIONS_AUTO_MIGRATE', true),
];
```

Advanced Usage
--------------

[](#advanced-usage)

### Manual Organization Switching

[](#manual-organization-switching)

```
use Inmanturbo\HomeworkOrganizations\OrganizationManager;

$manager = app(OrganizationManager::class);
$context = $manager->switch('org_123');

// Now all database queries use organization_org_123
// Storage paths point to organization_org_123 folders
```

### Custom Pipeline Actions

[](#custom-pipeline-actions)

Create a custom action to run during organization switching:

```
namespace App\Organizations\Actions;

use Closure;
use Inmanturbo\HomeworkOrganizations\OrganizationContext;

class CustomOrganizationAction
{
    public function handle(OrganizationContext $context, Closure $next)
    {
        // Your custom logic here
        // Example: Set organization-specific config values
        config(['app.name' => "App - {$context->organizationId}"]);

        // Pass context data between actions
        $context->data['custom_key'] = 'value';

        return $next($context);
    }
}
```

Register it in `config/organizations.php`:

```
'actions' => [
    \Inmanturbo\HomeworkOrganizations\Actions\SwitchOrganizationDatabase::class,
    \Inmanturbo\HomeworkOrganizations\Actions\SwitchOrganizationStorage::class,
    \App\Organizations\Actions\CustomOrganizationAction::class,
],
```

### Event Listeners

[](#event-listeners)

Listen to organization switching events:

```
use Illuminate\Support\Facades\Event;
use Inmanturbo\HomeworkOrganizations\Events\OrganizationSwitching;
use Inmanturbo\HomeworkOrganizations\Events\OrganizationSwitched;

Event::listen(OrganizationSwitching::class, function ($event) {
    // Before organization switch
    logger("Switching to organization: {$event->organizationId}");
});

Event::listen(OrganizationSwitched::class, function ($event) {
    // After organization switch
    logger("Switched to organization: {$event->organizationId}");
    // Access context data
    $database = $event->context->data['database'];
});
```

### Manual Organization Migration

[](#manual-organization-migration)

To manually migrate a specific organization's database:

```
php artisan organizations:migrate org_123
```

This command:

- Switches to the organization's database
- Runs all pending migrations
- Useful for testing or when auto-migration is disabled

### Disabling Auto-Migration

[](#disabling-auto-migration)

If you prefer manual control over organization migrations:

**.env:**

```
ORGANIZATIONS_AUTO_MIGRATE=false
```

Then use the `organizations:migrate` command for each organization as needed.

Architecture
------------

[](#architecture)

### Pipeline Pattern

[](#pipeline-pattern)

The package uses Laravel's Pipeline to execute a series of actions when switching organizations:

```
OrganizationManager::switch('org_123')
  → Fire OrganizationSwitching event
  → Create OrganizationContext
  → Run through Pipeline:
      → SwitchOrganizationDatabase
      → SwitchOrganizationStorage
      → (Your custom actions)
  → Fire OrganizationSwitched event
  → Return OrganizationContext

```

### Database Naming Convention

[](#database-naming-convention)

Organization databases follow a predictable pattern:

- Base database: `my_app` (from `config/database.php`)
- Organization ID: `org_123`
- Result: `my_app_org_123`

This convention allows:

- No external database credential provider needed
- Easy to understand and debug
- Automatic database creation on first access

### Primary Key Strategy

[](#primary-key-strategy)

User records use `workos_id` as the primary key instead of auto-increment IDs:

- **Why:** Prevents session collision across organizations (no duplicate ID 1, 2, 3...)
- **How:** WorkOS ID is globally unique across all organizations
- **Compatibility:** `getIdAttribute()` accessor maintains `$user->id` support

Roadmap
-------

[](#roadmap)

Future enhancements planned for this package:

- **External Database Provider** - Fetch credentials from external APIs
- **Per-Organization Database Drivers** - Organizations use different database types
- **Bring Your Own Database** - Organizations provide their own database credentials
- **Advanced Storage** - S3 buckets per organization, CDN integration
- **Organization Admin Access** - Direct database access for org admins

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT License. See LICENSE for details.

###  Health Score

28

—

LowBetter than 54% of packages

Maintenance64

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity37

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

217d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/0261babef618b8fb3bfcea84376ed5e71e7169586eb8de63a6550c2e7ea653a6?d=identicon)[inmanturbo](/maintainers/inmanturbo)

---

Top Contributors

[![inmanturbo](https://avatars.githubusercontent.com/u/47095624?v=4)](https://github.com/inmanturbo "inmanturbo (5 commits)")

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/inmanturbo-homework-organizations/health.svg)

```
[![Health](https://phpackages.com/badges/inmanturbo-homework-organizations/health.svg)](https://phpackages.com/packages/inmanturbo-homework-organizations)
```

###  Alternatives

[mongodb/laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel

7.1k7.2M71](/packages/mongodb-laravel-mongodb)[tucker-eric/eloquentfilter

An Eloquent way to filter Eloquent Models

1.8k4.8M26](/packages/tucker-eric-eloquentfilter)[roots/acorn

Framework for Roots WordPress projects built with Laravel components.

9682.1M97](/packages/roots-acorn)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

245.2k](/packages/aedart-athenaeum)[flarum/core

Delightfully simple forum software.

211.3M1.9k](/packages/flarum-core)[jerome/filterable

Streamline dynamic Eloquent query filtering with seamless API request integration and advanced caching strategies.

19226.1k](/packages/jerome-filterable)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
