API Platform Integration
Thelia 3 uses API Platform for its REST API. The API exposes all core e-commerce entities, and modules can extend it.
Architecture
┌─────────────────────────────────┐
│ External Clients │
│ (Mobile apps, SPAs, etc.) │
└───────────────┬─────────────────┘
│ HTTP/JSON
┌───────────────▼─────────────────┐
│ API Platform │
│ /api/admin/ & /api/front/ │
└───────────────┬─────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Resources │ │ Addons │ │ Filters │
│ (entities) │ │ (enrichment) │ │ (queries) │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└──────────────────────────┼──────────────────────────┘
│
┌───────────────▼─────────────────┐
│ Propel ORM │
│ (State Providers/Processors) │
└─────────────────────────────────┘
Key concepts
Resources
API resources are PHP classes that define how Propel models are exposed via the API.
#[ApiResource(
operations: [
new GetCollection(uriTemplate: '/admin/products'),
new Get(uriTemplate: '/admin/products/{id}'),
new Post(uriTemplate: '/admin/products'),
// ...
],
)]
class Product extends AbstractTranslatableResource
{
#[Groups([self::GROUP_ADMIN_READ])]
public ?int $id = null;
#[Groups([self::GROUP_ADMIN_READ, self::GROUP_ADMIN_WRITE])]
public string $ref;
// ...
}
See Resources for full documentation.
Addons
Addons enrich existing resources with additional data without modifying the core.
class CustomerCustomerFamily implements ResourceAddonInterface
{
#[Groups([Customer::GROUP_ADMIN_READ])]
public ?string $familyCode = null;
public static function getResourceParent(): string
{
return Customer::class;
}
// ...
}
See Addons for full documentation.
Serialization groups
Control which fields are visible in each context:
| Group Pattern | Description |
|---|---|
admin:*:read | Admin read operations |
admin:*:write | Admin write operations |
front:*:read | Public front-office |
*:single | Single item only (not collections) |
See Serialization Groups for details.
Filters
Filter, sort, and search API collections:
GET /api/front/products?visible=true&brand.id=5&order[position]=asc
See Filters for available filters.
Route namespaces
Admin routes (/api/admin/)
Full CRUD operations for authenticated administrators:
GET /api/admin/products # List products
POST /api/admin/products # Create product
GET /api/admin/products/{id} # Get single product
PUT /api/admin/products/{id} # Update product
DELETE /api/admin/products/{id} # Delete product
Front routes (/api/front/)
Read-only access for public consumption:
GET /api/front/products # List visible products
GET /api/front/products/{id} # Get single product
Authentication
The API uses JWT (JSON Web Token) authentication via lexik/jwt-authentication-bundle.
See Authentication for details on:
- JWT token authentication
- Login endpoints (
/api/admin/login,/api/front/login) - CORS configuration
Internal access: DataAccessService
Templates and components can call the API internally, without HTTP overhead:
{% set products = resources('/api/front/products', {
'productCategories.category.id': categoryId,
'visible': true,
'itemsPerPage': 20
}) %}
$products = $this->dataAccessService->resources('/api/front/products', [
'productCategories.category.id' => $categoryId,
]);
Response format
Default format (JSON)
API responses return simple JSON arrays by default. Translatable fields are nested under i18ns, keyed by locale:
[
{
"id": 1,
"ref": "PROD-001",
"visible": true,
"i18ns": {
"en_US": {
"title": "My Product",
"description": "Product description..."
},
"fr_FR": {
"title": "Mon produit",
"description": "Description du produit..."
}
}
},
{
"id": 2,
"ref": "PROD-002",
"visible": true,
"i18ns": {
"en_US": {
"title": "Another Product",
"description": "..."
},
"fr_FR": {
"title": "Un autre produit",
"description": "..."
}
}
}
]
Over HTTP, the API always returns i18ns keyed by locale ("i18ns": { "en_US": {...}, "fr_FR": {...} }). The HTTP response is never flattened to a single locale.
The flattened single-locale shape ("i18ns": { "title": ... }) only appears when you consume the API internally, through DataAccessService::resources() or the resources() Twig function. On that path, ResourceService::resources() finishes by calling formatI18ns(), which collapses i18ns down to the current locale's sub-array. This happens for both the JSON and jsonld formats. The locale used is the one resolved by LocaleService; pass an explicit locale parameter to change which locale is kept.
JSON-LD format (with Hydra metadata)
To get pagination metadata (total items, page info), request the JSON-LD format with the Accept: application/ld+json header:
{
"@context": "/api/contexts/Product",
"@id": "/api/front/products",
"@type": "hydra:Collection",
"hydra:totalItems": 42,
"hydra:member": [
{
"@id": "/api/front/products/1",
"@type": "Product",
"id": 1,
"ref": "PROD-001"
}
],
"hydra:view": {
"@id": "/api/front/products?page=1",
"hydra:first": "/api/front/products?page=1",
"hydra:last": "/api/front/products?page=3",
"hydra:next": "/api/front/products?page=2"
}
}
DataAccessService (PHP/Twig)
When using DataAccessService::resources() internally, the default format is JSON (simple arrays):
// Default: returns simple array
$products = $this->dataAccessService->resources('/api/front/products');
// Result: [['id' => 1, 'ref' => 'PROD-001', ...], ...]
// JSON-LD: returns Hydra structure with pagination metadata
$response = $this->dataAccessService->resources('/api/front/products', [], 'jsonld');
// Result: ['hydra:member' => [...], 'hydra:totalItems' => 42, ...]
Use 'jsonld' format only when you need pagination metadata (total items, page info).
Available endpoints
A summary of the most common resources. The front routes for Customers and Orders require customer authentication; they only expose the authenticated customer's own data.
| Entity | Admin Route | Front Route |
|---|---|---|
| Products | /api/admin/products | /api/front/products |
| Categories | /api/admin/categories | /api/front/categories |
| Customers | /api/admin/customers | /api/front/customers (POST), /api/front/account/customers/{id} |
| Orders | /api/admin/orders | /api/front/account/orders |
| Cart | /api/admin/carts | /api/front/carts, /api/front/cart |
| Brands | /api/admin/brands | /api/front/brands |
See Endpoints Reference for the complete API documentation.
Creating custom resources
Modules can add their own API resources:
// MyModule/Api/Resource/ProductReview.php
#[ApiResource(
operations: [
new GetCollection(uriTemplate: '/front/product_reviews'),
new Post(uriTemplate: '/front/product_reviews'),
],
)]
class ProductReview implements PropelResourceInterface
{
use PropelResourceTrait;
#[Groups(['front:product_review:read'])]
public ?int $id = null;
#[Groups(['front:product_review:read', 'front:product_review:write'])]
public string $content;
// ...
}
See Module Structure: API Components for module-specific documentation.
Next steps
- Authentication - Secure your API access
- Resources - Create API resources
- Addons - Extend existing resources
- Filters - Query and filter data
- Endpoints Reference - Complete API reference