Cross-Origin Resource Sharing (CORS) is a critical security feature that controls how web resources are accessed from different domains. In the modern era of decoupled Drupal, headless architectures, and mobile application integrations, mastering Drupal CORS configuration is essential for any developer. If you have ever encountered the frustrating 'No Access-Control-Allow-Origin header is present' error in your browser console while trying to fetch data from your Drupal backend, this guide is for you.
Since Drupal 8.2, core has included built-in support for CORS through Symfony services. This means you no longer need to rely on external modules for basic CORS functionality. Instead, you can manage these settings directly within your site's configuration files. In this tutorial, we will walk through the exact steps to enable and configure CORS in Drupal to ensure your RESTful web services work flawlessly across different environments.
Understanding CORS in the Drupal Ecosystem
CORS is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin access to selected resources from a different origin. By default, Drupal (and most web servers) blocks these requests to prevent malicious scripts from accessing sensitive data.
When you build a React, Vue, or Angular frontend that lives on a separate domain (e.g., frontend.example.com) from your Drupal backend (api.example.com), the browser will trigger a preflight OPTIONS request. If your Drupal site isn't configured to respond correctly to this request, the connection will fail. Drupal manages this through the services.yml file, which defines how the underlying Symfony framework handles cross-origin requests.
Step 1: Initializing Your services.yml File
Before you can configure CORS, you must ensure you are working with the correct file. Drupal provides a template, but it is not active by default. You must create your own configuration file to override the core defaults.
- Navigate to your site's directory:
/sites/default/. - Locate the file named
default.services.yml. - Make a copy of this file and rename it to
services.yml.
Important Note on File Location: Ensure that your services.yml file is located specifically in the /sites/default/ directory. While some developers assume it can live in the root /sites/ folder, Drupal specifically looks for the site-specific service definitions within the default or multi-site subdirectory. If you are working on a hosted environment like Pantheon, this distinction is vital for the configuration to take effect.
Step 2: Enabling the CORS Configuration
Open your newly created services.yml file in your preferred text editor. Search for the cors.config section. By default, it will likely be commented out or set to enabled: false. You need to change this to true and define your parameters.
Here is a robust configuration example that works well for development environments, particularly when testing across local domains and remote sandboxes:
cors.config:
enabled: true
# Specify allowed headers, like 'x-allowed-header'.
allowedHeaders: ['x-csrf-token','authorization','content-type','accept','origin','x-requested-with', 'access-control-allow-origin','x-allowed-header','*']
# Specify allowed request methods, specify ['*'] to allow all possible ones.
allowedMethods: ['*']
# Configure requests allowed from specific origins.
allowedOrigins: ['http://localhost/','http://localhost:3000','http://localhost:3001','http://localhost:3002','*']
# Sets the Access-Control-Expose-Headers header.
exposedHeaders: false
# Sets the Access-Control-Max-Age header.
maxAge: false
# Sets the Access-Control-Allow-Credentials header.
supportsCredentials: true
Breaking Down the Configuration
- allowedHeaders: This is where many developers stumble. You must list every header your client application intends to send. Common requirements include
x-csrf-token(for authenticated POST requests) andauthorization. Notice that each header is enclosed in its own set of single quotes within the array. Avoid using a single comma-separated string, as this can cause the YAML parser to misinterpret the list. - allowedMethods: This defines which HTTP verbs are permitted. Using
['*']allows everything (GET, POST, PATCH, DELETE, OPTIONS), which is great for development but should be restricted in production. - allowedOrigins: This lists the domains permitted to access your API. For local development, you might include
http://localhost:3000. Using['*']allows any domain, which should only be used if your API is intended to be public. - supportsCredentials: If your application uses cookies or session-based authentication, this must be set to
true.
Step 3: Troubleshooting Common CORS Issues
Even with the correct settings, you may encounter issues. Here are the most common pitfalls and how to fix them:
Syntax Errors in YAML
One of the most frequent causes of failure is improper YAML syntax. Ensure your indentation is consistent (use spaces, not tabs). As mentioned previously, ensure your arrays are properly formatted.
Incorrect:
allowedHeaders: ['x-csrf-token,authorization,content-type']
Correct:
allowedHeaders: ['x-csrf-token', 'authorization', 'content-type']
The exposedHeaders Trap
Setting exposedHeaders: true is a common mistake. According to the W3C standards and the Symfony implementation, this value must either be false or an array of specific headers you wish to expose to the browser. Setting it to true will trigger a PHP warning and potentially break your API response.
Clearing the Cache
Drupal caches the service container. Any changes you make to services.yml will not be recognized until you clear the cache. Use Drush for a quick reset:
drush cr
Advanced Approach: Dynamic CORS via Event Subscribers
If your project requires more dynamic control—such as allowing origins based on database values or complex logic—you might find the services.yml file too static. In these cases, you can implement a custom Event Subscriber.
By subscribing to the kernel.request and kernel.response events, you can manually set the Access-Control-Allow-Origin and Access-Control-Allow-Headers headers in code. This is particularly useful for multi-tenant applications where allowed origins change frequently.
Frequently Asked Questions
Why is my services.yml change not showing up?
First, verify the file is in sites/default/services.yml. Second, ensure you have cleared the Drupal cache (drush cr). Finally, check if your web server (Nginx or Apache) is overriding headers. Sometimes a server-level configuration can conflict with Drupal's application-level settings.
Is it safe to use '*' for allowedOrigins?
Using a wildcard * is acceptable for public read-only APIs. However, if your API handles sensitive user data or allows state-changing operations (POST, DELETE), you should explicitly list your trusted domains to prevent Cross-Site Request Forgery (CSRF) and other cross-origin attacks.
Do I still need the CORS module in Drupal 9 or 10?
Generally, no. The core functionality provided by the Symfony framework and the services.yml configuration is sufficient for most use cases. The CORS module is largely legacy software for versions of Drupal prior to 8.2 or for users who prefer a UI-based configuration over editing YAML files.
Wrapping Up
Configuring CORS in Drupal doesn't have to be a trial-and-error process. By correctly utilizing the services.yml file in your sites/default directory and paying close attention to array syntax and header requirements, you can establish a secure and functional bridge between your Drupal backend and any external frontend.
Remember to start with a broad configuration during the initial development phase to rule out connectivity issues, but always tighten your allowedOrigins and allowedMethods before deploying to a production environment. With these steps, your Drupal API is ready to power the next generation of web applications.