laravelui5 / odata
Multi-OData endpoint support for Laravel
Requires
- php: ^8.4
- ext-dom: *
- ext-json: *
- ext-libxml: *
- ext-simplexml: *
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/http: ^11.0 || ^12.0 || ^13.0
- illuminate/routing: ^11.0 || ^12.0 || ^13.0
Requires (Dev)
- ext-pdo_mysql: *
- ext-pdo_pgsql: *
- ext-pdo_sqlite: *
- brianium/paratest: ^7.0
- eclipxe/xmlschemavalidator: ^3.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-arch: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
- phpstan/phpstan: ^1.0.0
- phpunit/phpunit: ^11.0
- sebastian/diff: ^6.0
- staudenmeir/eloquent-json-relations: ^1.0
README
A read-only OData v4 engine for Laravel. Point it at an Eloquent model and every client that speaks OData — a UI5 table, Excel, Power BI, your own frontend — can filter, sort, page, project, and traverse your data over plain HTTP. No controllers, no serializers, no query parameters you have to invent and then document.
The problem it solves
Every Laravel app that grows a frontend grows an API, and every one of them re-invents the same
five things: projection, filtering, sorting, paging, and a schema nobody can trust. Controllers,
resources, query scopes, an ?include= convention, and a document that starts drifting in week
three. For every project. Every time.
OData already answers all five. It is an OASIS Standard, published by ISO as ISO/IEC 20802, and spoken natively by SAP, Microsoft, Salesforce, Excel, and every UI5 SmartControl. This package brings it to Laravel:
GET /odata/Products?$filter=price gt 10&$orderby=name&$top=20&$select=name,price
GET /odata/Products(42)?$expand=supplier
GET /odata/$metadata
Those URLs work the moment you declare a service. The last one returns a machine-readable schema, generated from the same model that serves the data — so it cannot drift from the API it describes.
Not sure OData is the right call for your project? Why OData? makes the argument, and names the cases where the answer is something else.
What you get
- Full query support —
$filter,$select,$expand(nested),$orderby,$top,$skip,$count,$search,$compute, and$batchwith partial failure - A real schema —
$metadataas CSDL XML, service documents, functions, singletons, and annotations that carry meaning, not just types - Any backing store — an Eloquent model, a SQL view, an external API, a directory of files; the key is the only hard requirement
- Multiple services in one application, each on its own route
- A read-authorization seam — bind one interface to gate entity sets per actor: a denied root
answers
403, a denied$expandis pruned with a warning instead of failing the whole read - Compiled schemas —
php artisan odata:cachepre-compiles the EDM to PHP classes, so no discovery happens at request time - Streamed responses — large result sets never buffer in memory
Read-only by design: queries in, JSON out. Writes stay in your application, where validation and
business rules belong. Also out of scope: ETags, $apply, and OData actions.
Requirements
PHP 8.4+ · Laravel 11, 12, or 13 · the dom, json, libxml, and simplexml extensions
Install
composer require laravelui5/odata
php artisan vendor:publish --provider="LaravelUi5\OData\ODataServiceProvider"
The service provider registers itself through Laravel's auto-discovery. The published
config/odata.php controls the route prefix, middleware, streaming, page sizes, the service
registry, and read authorization — see
Installation and the
Configuration reference.
Quickstart
Declare a service:
use LaravelUi5\OData\ODataService; use LaravelUi5\OData\Service\Contracts\EdmBuilderInterface; class ProductService extends ODataService { public function serviceUri(): string { return ''; } public function namespace(): string { return 'App.Products'; } protected function configure(EdmBuilderInterface $builder): EdmBuilderInterface { $this->discoverModel(Product::class); return $builder->namespace($this->namespace()); } }
discoverModel() reads the table, maps columns to typed properties, detects the key, and turns
Eloquent relationships into navigation properties. Point config/odata.php at a registry that
returns the service, and /odata/Products is live.
The Quickstart walks the whole path
in five minutes, from make:model to the first $filter.
Talk to it from anything
The API is URLs, so the smallest possible client is fetch:
const res = await fetch( "/odata/Products?$filter=active eq true&$orderby=price desc&$top=10", { headers: { Accept: "application/json" } }, ); const { value: products } = await res.json();
Ready-made clients exist where you would want them. UI5's sap.ui.model.odata.v4.ODataModel binds
tables and forms straight to an entity set — see the
Smart Table and
Value Help recipes. Excel and Power BI
consume a service as an OData feed out of the box (Get Data → From OData feed). And npm carries
typed clients such as @odata/client and
ra-data-odata-server for react-admin.
Documentation
Full documentation lives at laravelui5.com/odata.
| Section | What's there |
|---|---|
| Getting Started | why OData, the concepts, installation, quickstart |
| Services | defining a service, model discovery, manual schema, functions, multi-service |
| Resolvers | Eloquent, raw SQL, custom entity sets, custom resolvers |
| Query Options | $filter, $select/$expand, $orderby, paging, $search, $count, $compute |
| Metadata & Annotations | $metadata, service document, vocabulary terms |
| Advanced | architecture, configuration, $batch, caching, error handling |
| Recipes | UI5 Smart Table, value help, testing |
| API Reference | generated class reference |
Support
- Bugs and feature requests — github.com/laravelui5/odata/issues
- Security — please do not open a public issue; see SECURITY.md
- What shipped — CHANGELOG.md · what's queued — ROADMAP.md
- The wider stack — this engine is the MIT foundation of LaravelUi5: Core adds UI5 artifact routing, the SDK adds the enterprise runtime
Provenance
A clean-room rewrite of flat3/lodata. Its protocol test suite was the pivot: ~400 HTTP tests that define the OData wire contract this implementation has to honor. No original implementation code was preserved; the refactored tests remain as the permanent regression suite.
License
MIT — see LICENSE.