PHPackages                             sefirosweb/laravel-odoo-connector - 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. sefirosweb/laravel-odoo-connector

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

sefirosweb/laravel-odoo-connector
=================================

Driver to connect Odoo using ORM of laravel

v13.0.0(1mo ago)3631↓86.6%2MITPHPPHP ^8.3CI passing

Since Aug 17Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/sefirosweb/laravel-odoo-connector)[ Packagist](https://packagist.org/packages/sefirosweb/laravel-odoo-connector)[ RSS](/packages/sefirosweb-laravel-odoo-connector/feed)WikiDiscussions 13.x Synced yesterday

READMEChangelogDependencies (3)Versions (43)Used By (0)

Laravel Odoo Connector
======================

[](#laravel-odoo-connector)

Use Laravel's Eloquent ORM against an [Odoo](https://www.odoo.com/) instance, through Odoo's JSON-RPC API.

Reference: [Odoo Web Services Documentation (JSON-RPC)](https://www.odoo.com/documentation/master/developer/howtos/web_services.html).

Why JSON-RPC instead of a raw PostgreSQL connection?
----------------------------------------------------

[](#why-json-rpc-instead-of-a-raw-postgresql-connection)

Connecting Laravel directly to Odoo's Postgres database is simpler on paper but bypasses Odoo's business logic. Every time Odoo's ORM handles a write it runs a chain of Python-side triggers — invoice generation, stock moves, mail activities, etc. — that a raw SQL `INSERT` will silently skip.

This package goes through Odoo's JSON-RPC layer so those side effects still fire. It also lets you call **model actions** (the equivalent of clicking a button in the Odoo UI), which is impossible from raw SQL:

```
$saleOrder = SaleOrder::find(1);
$saleOrder->action('action_confirm');
// Triggers the "Confirm" button on sale.order — see Odoo's sale/models/sale_order.py
```

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

[](#requirements)

- PHP `^8.2`
- Laravel `^12.0`
- An Odoo instance with JSON-RPC access enabled and valid credentials (username + API key or password).

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

[](#installation)

```
composer require sefirosweb/laravel-odoo-connector:^12.0
```

The service provider auto-registers via Laravel's package discovery and registers an `odoo` driver on Laravel's connection manager.

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

[](#configuration)

### 1. Declare the connection in `config/database.php`

[](#1-declare-the-connection-in-configdatabasephp)

```
'connections' => [
    // ...
    'odoo' => [
        'driver'   => 'odoo',
        'host'     => env('ODOO_HOST',     'https://your-odoo-host.com'),
        'database' => env('ODOO_DB',       'db_name'),
        'username' => env('ODOO_USERNAME', 'user'),
        'password' => env('ODOO_PASSWORD', 'api_key'),
        'defaultOptions' => [
            'timeout' => 20,
            'context' => [
                'lang' => 'es_ES',
            ],
        ],
    ],
],
```

And in `.env`:

```
ODOO_HOST=https://your-odoo-host.com
ODOO_DB=db_name
ODOO_USERNAME=user
ODOO_PASSWORD=api_key
```

### 2. (Optional) Publish the config to override shipped models

[](#2-optional-publish-the-config-to-override-shipped-models)

```
php artisan vendor:publish --provider="Sefirosweb\LaravelOdooConnector\LaravelOdooConnectorServiceProvider" --tag=config --force
```

Then in `config/laravel-odoo-connector.php`:

```
return [
    'ProductProduct'  => App\Odoo\CustomProductProduct::class,
    'ProductTemplate' => Sefirosweb\LaravelOdooConnector\Http\Models\ProductTemplate::class,
    'ResLang'         => Sefirosweb\LaravelOdooConnector\Http\Models\ResLang::class,
    // ...
];
```

Every model in the package resolves related classes through this config, so replacing `ProductProduct` here propagates to any relation that targets it.

### 3. Test the connection

[](#3-test-the-connection)

```
php artisan test:odoo
```

This runs a smoke test that fetches the first `MrpProduction` and dumps its `mrp_immediate_production_lines` relation. It's a quick way to verify auth + connectivity.

Usage
-----

[](#usage)

### Basic Eloquent

[](#basic-eloquent)

The shipped models under `Sefirosweb\LaravelOdooConnector\Http\Models\*` cover the most common Odoo models (product, mrp, sale, purchase, stock, mail, etc.). Use them like any Eloquent model:

```
use Sefirosweb\LaravelOdooConnector\Http\Models\ProductProduct;

$products = ProductProduct::where('name', 'like', '%widget%')
    ->with('mrp_bom')
    ->get();

$product = ProductProduct::find(1);
$product->name = 'New name';
$product->save();

$created = ProductProduct::create([
    'name'        => 'Product X',
    'description' => 'Flagship SKU',
    'list_price'  => 100,
]);
```

Supported Eloquent methods include `find`, `where`, `whereIn`, `whereNotIn`, `whereNull`, `whereNotNull`, `with`, `create`, `update`, `delete`, `get`, `first`, `limit`, `offset`, `orderBy`, etc.

### Known limitations

[](#known-limitations)

Odoo's domain filter language is more restrictive than SQL, so a few Eloquent primitives cannot be translated:

- **`whereHas()` / `has()` / `whereDoesntHave()`** — these compile to a correlated `EXISTS` subquery which Odoo cannot express. The driver throws `OdooUnsupportedOperationException` with an actionable message pointing you at the workaround: resolve the related ids first and pass them to `whereIn()`:

    ```
    // ❌ Will throw OdooUnsupportedOperationException
    $orders = SaleOrder::whereHas('sale_order_lines')->get();

    // ✅ Two-step pattern that works
    $orderIds = SaleOrderLine::select('order_id')->pluck('order_id');
    $orders   = SaleOrder::whereIn('id', $orderIds)->get();
    ```
- **many2one fields are returned as `[id, display_name]` tuples**, not plain ids. This is how Odoo JSON-RPC serialises them and the driver does not unwrap them for you. If you need just the id, access `[0]`:

    ```
    $line = SaleOrderLine::first();
    $orderId = is_array($line->order_id) ? $line->order_id[0] : $line->order_id;
    ```
- **Empty char / text fields come back as `false`**, not `null` / `""`. Another Odoo serialisation quirk; check with `=== false` when that matters.
- **`between`, `whereDate`, `whereYear`, raw SQL expressions** and other where types that require SQL-side evaluation will also throw `OdooUnsupportedOperationException` at compile time.

### Customising a model

[](#customising-a-model)

Extend the shipped model and register your override through the config:

```
namespace App\Odoo;

use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Sefirosweb\LaravelOdooConnector\Http\Models\ProductProduct as BaseProductProduct;

class CustomProductProduct extends BaseProductProduct
{
    protected $table = 'product.product';

    public function our_custom_belongs(): BelongsTo
    {
        return $this->belongsTo(OurCustomModel::class, 'our_field_id');
    }
}
```

Then point `config/laravel-odoo-connector.php:ProductProduct` at `App\Odoo\CustomProductProduct::class`.

### Soft-deletes (the Odoo `active` flag)

[](#soft-deletes-the-odoo-active-flag)

Odoo's equivalent of a soft-delete is the `active` boolean column. Mirror it with the shipped trait:

```
use Sefirosweb\LaravelOdooConnector\Http\Models\OdooModel;
use Sefirosweb\LaravelOdooConnector\Http\Traits\SoftDeleteOdoo;

class MyModel extends OdooModel
{
    use SoftDeleteOdoo;
    // ...
}
```

### Fetching large collections

[](#fetching-large-collections)

Odoo JSON-RPC times out on unbounded queries. Use `get_all` to chunk automatically (500 records per batch by default):

```
$products = ProductProduct::get_all('id', 'name', 100);
// Behaves like ::all() but paginates under the hood.
```

### Model actions (Odoo server-side buttons)

[](#model-actions-odoo-server-side-buttons)

Trigger the equivalent of a UI button:

```
$saleOrder = SaleOrder::find(1);
$saleOrder->action('action_confirm');
```

Pass extra arguments when the action needs them:

```
$args = [[['id' => 1]]];
SaleOrder::model_action('action_custom', $args);
```

### Multiple Odoo connections

[](#multiple-odoo-connections)

Define multiple `odoo` connections in `config/database.php` and target one from a model:

```
use Sefirosweb\LaravelOdooConnector\Http\Models\OdooModel;

class SecondaryOdooModel extends OdooModel
{
    protected $connection = 'other_odoo';
}
```

Or run a one-off query against a named connection:

```
ProductProduct::on('other_odoo')->where(...)->get();
```

Testing
-------

[](#testing)

The test suite is split in two:

- **`Feature`** — offline smoke tests (service provider boot, `DB::extend('odoo', …)` registration). Always run; no external dependencies.
- **`Integration`** — hit a real Odoo instance over JSON-RPC. Cover authentication, `ResPartner`, `ProductProduct`, `SaleOrder` + relations, `PurchaseOrder`, and the actionable error paths (`whereHas` / bad credentials). Auto-skip when the `ODOO_*` environment variables are not set.

### Run the offline suite only

[](#run-the-offline-suite-only)

```
composer install
./vendor/bin/phpunit --testsuite Feature
```

### Run the integration suite

[](#run-the-integration-suite)

Set the four required variables in your shell or load them from a local `.env.testing`:

```
export ODOO_HOST=https://your-odoo-staging.example
export ODOO_DB=odoo_staging
export ODOO_USERNAME=integration-tests@example.com
export ODOO_PASSWORD=your-api-key
# Optional:
export ODOO_TIMEOUT=20
export ODOO_LANG=en_US

./vendor/bin/phpunit --testsuite Integration
```

Integration tests are **read-only** — they never create, update or delete rows — so they are safe to run against a production-copy Odoo, though a staging instance is preferable.

### Run both suites

[](#run-both-suites)

```
./vendor/bin/phpunit
```

When working from the [laravel-test](https://github.com/sefirosweb/laravel-test) harness with Sail, pass the env vars through `docker exec`:

```
docker exec -w /var/www/html/packages/laravel-odoo-connector \
  -e ODOO_HOST=$ODOO_HOST \
  -e ODOO_DB=$ODOO_DB \
  -e ODOO_USERNAME=$ODOO_USERNAME \
  -e ODOO_PASSWORD=$ODOO_PASSWORD \
  laravel-test-laravel.test-1 ./vendor/bin/phpunit
```

Roadmap
-------

[](#roadmap)

- Add the remaining Odoo models that individual projects depend on (custom Odoo installs vary per client, so the package ships only the "standard" models).
- Optionally auto-unwrap many2one tuples to plain ids inside `OdooProcessor` (would change the public contract, so gated behind a config flag).

Versioning
----------

[](#versioning)

Major versions are aligned with Laravel majors (`12.x`, `11.x`, `9.x` …). See the root [CLAUDE.md](https://github.com/sefirosweb/laravel-test/blob/12.0/CLAUDE.md) of the test harness for the full policy.

License
-------

[](#license)

MIT.

###  Health Score

51

—

FairBetter than 95% of packages

Maintenance89

Actively maintained with recent releases

Popularity23

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity67

Established project with proven stability

 Bus Factor1

Top contributor holds 95.2% 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

Every ~15 days

Recently: every ~4 days

Total

42

Last Release

55d ago

Major Versions

v1.3.3 → 9.x-dev2025-09-16

9.x-dev → v12.0.02026-04-23

12.x-dev → v13.0.02026-05-09

PHP version history (2 changes)v12.0.0PHP ^8.2

13.x-devPHP ^8.3

### Community

Maintainers

![](https://www.gravatar.com/avatar/e00c36bae7e59a7375c9bc0e0280ef4004afa4ca968ea6cd62f7d880df2dbd66?d=identicon)[sefirosweb](/maintainers/sefirosweb)

---

Top Contributors

[![sefirosweb](https://avatars.githubusercontent.com/u/20754836?v=4)](https://github.com/sefirosweb "sefirosweb (59 commits)")[![MElkmeshi](https://avatars.githubusercontent.com/u/95718825?v=4)](https://github.com/MElkmeshi "MElkmeshi (3 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/sefirosweb-laravel-odoo-connector/health.svg)

```
[![Health](https://phpackages.com/badges/sefirosweb-laravel-odoo-connector/health.svg)](https://phpackages.com/packages/sefirosweb-laravel-odoo-connector)
```

###  Alternatives

[anourvalar/eloquent-serialize

Laravel Query Builder (Eloquent) serialization

11223.5M33](/packages/anourvalar-eloquent-serialize)[statamic-rad-pack/runway

Eloquently manage your database models in Statamic.

135224.7k7](/packages/statamic-rad-pack-runway)[api-platform/laravel

API Platform support for Laravel

58171.4k14](/packages/api-platform-laravel)[duncanmcclean/statamic-cargo

Comprehensive e-commerce addon for Statamic. Build bespoke e-commerce sites without the complexity.

3416.9k](/packages/duncanmcclean-statamic-cargo)

PHPackages © 2026

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