Shopware’s built-in REST and GraphQL APIs cover the majority of e-commerce integration scenarios, but production environments frequently demand tailored data exposure, external system synchronization, or domain-specific business logic. Starting with Shopware 6.7, the platform has significantly refined its API architecture to align with modern Symfony conventions, introducing stricter typing, improved route resolution, and a more transparent permission model. Building custom endpoints in 6.7+ is not only more predictable but also enforces better separation of concerns from day one. This guide walks you through the architectural decisions, routing mechanics, security considerations, and response formatting required to implement a production-ready custom API endpoint.

Understanding Shopware 6.7’s API Architecture Shifts

Before writing code, it’s essential to understand what changed in version 6.7. The framework has fully embraced PHP attributes for route definition, deprecating legacy @Route annotations in new installations. More importantly, 6.7 enforces a clearer boundary between authentication (who is making the request) and authorization (what they are allowed to access). Custom API endpoints now inherit their security context explicitly through route defaults, and permission scopes must be declared declaratively rather than enforced through manual checks. The Data Abstraction Layer (DAL) remains the only supported path for data retrieval, and response serialization follows strict OpenAPI-aligned conventions. These changes mean your endpoint will be more secure, easier to maintain, and fully compatible with Shopware’s future deprecation roadmap.

Plugin Structure & Registration

Shopware expects all custom API logic to live inside a properly structured plugin. Create a root directory under src/Plugins/CustomApiEndpoint/ and include the mandatory CustomApiEndpoint.php registration file. This class must extend Shopware\Core\Framework\Plugin and override the boot() method to register your routes. In 6.7+, you no longer need to manually modify global configuration files; route registration is encapsulated within the plugin itself, making it portable and version-safe.

use Shopware\Core\Framework\Plugin;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class CustomApiEndpoint extends Plugin
{
    public function boot(ContainerBuilder $container): void
    {
        parent::boot($container);
        $this->registerRoutes();
    }
}

The registerRoutes() method is where Shopware 6.7’s routing layer integrates with your custom controller. Instead of relying on external YAML files, you pass the bundle path directly to the framework’s route loader. This keeps API definitions co-located with their implementation and reduces configuration drift during deployments.

Routing & Controller Setup

Custom API endpoints are implemented as standard Symfony controllers that extend Shopware\Core\Framework\Api\Controller\AbstractController. The primary difference between a regular controller and an API controller lies in how they handle serialization, context resolution, and response formatting. In 6.7+, you define routes using PHP attributes for forward compatibility:

use Shopware\Core\Framework\Api\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class CustomExtendedProductsController extends AbstractController
{
    #[Route(
        path: '/api/custom/products/extended',
        name: 'api.custom.products.extended',
        defaults: ['_auth' => 'route'],
        methods: ['GET']
    )]
    public function index(Request $request): Response
    {
        // Implementation follows
    }
}

The _auth parameter dictates the security context. Setting it to route makes the endpoint publicly accessible, while admin or store restricts it to their respective API namespaces. Shopware 6.7 validates this during route compilation and will throw a configuration exception if you reference an undefined authentication type. Always prefer explicit HTTP method constraints over relying on Symfony’s fallback behavior; this prevents unintended cross-method exposure and aligns with RESTful standards.

Authentication, Permissions & Security Context

Authorization in Shopware 6.7 is no longer a manual checkbox. Once your route declares its _auth value, the framework automatically resolves the requesting user’s API scope via the firewall layer. If your endpoint requires additional permissions beyond the base scope, you must declare them in your plugin’s info.xml under the <api-scopes> node. For example:

<api-scopes>
    <scope name="custom_extended_products" allowed=["read"] />
</api-scopes>

This declaration ensures that admin users see a permission toggle during role configuration, and storefront users receive a structured 403 Forbidden instead of silent data stripping. Shopware 6.7 also introduces granular audit logging for API requests. Every call to your endpoint is recorded in the admin panel’s API monitoring section, enabling you to track usage patterns, identify bottlenecks, and detect anomalous behavior without instrumenting custom loggers.

Request Handling & DAL Integration

Inside the controller method, avoid direct database queries or ORM usage. Shopware 6.7 enforces strict adherence to the Data Abstraction Layer for all data operations. Inject repositories through constructor dependency injection:

public function __construct(
    private readonly ProductRepositoryInterface $productRepository,
    private readonly ContextAccessorInterface $contextAccessor
) {}

The ContextAccessorInterface is critical in 6.7+ because it resolves the current request’s context (language, currency, sales channel, API scope) without relying on session state or global variables. Use it to fetch data through the repository pattern, which automatically applies field filtering, inheritance resolution, and permission-aware row restrictions. Always validate incoming query parameters using Symfony’s Validator component or Shopware’s built-in ApiQueryParameter constraints to prevent injection risks and malformed payloads.

Response Formatting & Error Handling

Shopware 6.7 expects API responses to follow a consistent structure. Raw arrays or untyped objects will break OpenAPI generation and storefront hydration. Wrap your data in ApiResult or use the framework’s RestResponseFactory to ensure proper serialization:

use Shopware\Core\Framework\Api\Dto\ApiResult;
use Shopware\Core\Framework\Api\Response\RestResponseFactory;
use Symfony\Component\HttpFoundation\JsonResponse;

// Inside your controller method:
$result = ApiResult::wrapData(
    $products,
    'Product',
    true,
    false,
    new RestResponseFactory()
);

return new JsonResponse($result->getEncoded());

The ApiResult wrapper handles pagination metadata, field filtering, and context-aware serialization automatically. When errors occur, never return plain JSON strings or HTTP exceptions outside the framework’s exception handler. Instead, throw Shopware\Core\Framework\Api\Exception\ApiException with structured error codes. This ensures consistent error payloads across all endpoints and enables frontend consumers to parse failures predictably.

Caching, Testing & Best Practices

Shopware 6.7 aggressively caches route definitions during development. After implementing your controller, always run bin/console cache:clear and verify that the route appears in bin/console router:debug. Test your endpoint using authenticated API tokens rather than admin sessions; this mirrors production behavior and exposes permission misconfigurations early.

Adopt these architectural habits in 6.7+:

  • Version your paths (/api/v1/custom/...) to enable graceful deprecation without breaking client integrations.
  • Keep business logic in service classes, not controllers. Controllers should only parse requests, invoke services, and format responses.
  • Document endpoints using Shopware’s OpenAPI generator by adding metadata attributes that describe parameters, response schemas, and security requirements.
  • Avoid returning raw Product entities; always filter fields to reduce payload size and prevent accidental data exposure.

Conclusion

Creating custom API endpoints in Shopware 6.7+ requires less configuration boilerplate but demands stricter adherence to routing conventions, permission scoping, and DAL workflows. By leveraging PHP attributes, explicit authentication defaults, and framework-provided response factories, you build endpoints that are secure, auditable, and fully aligned with Shopware’s evolution toward a modern Symfony stack. As your integration landscape grows, abstract validation, serialization, and error handling into reusable components. With 6.7’s refined API architecture, custom endpoints have transitioned from workaround solutions to first-class extension points, empowering developers to expose exactly what their ecosystems need without compromising platform stability.