laravelui5/odata

Multi-OData endpoint support for Laravel

Maintainers

Package info

github.com/laravelui5/odata

pkg:composer/laravelui5/odata

Transparency log

Statistics

Installs: 65

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v3.0.5 2026-08-28 15:38 UTC

README

Packagist Version PHP Version License

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 $batch with partial failure
  • A real schema$metadata as 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 $expand is pruned with a warning instead of failing the whole read
  • Compiled schemasphp artisan odata:cache pre-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

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.