Modern web applications routinely span multiple domains. Your frontend runs on app.yourcompany.com, your Form.io API lives at perhaps yourproject.form.io or a self-hosted endpoint, and your forms embed into pages served from yet another origin. Without proper configuration, browsers block these cross-domain requests as a security measure. Form.io provides per-project CORS configurations that tell browsers which domains have permission to access form definitions and submission data.
This is not optional configuration for most deployments. If your application domain differs from your Form.io API domain, you need CORS configured correctly, or your forms will fail to load and submissions will fail to save. Understanding how CORS works, what Form.io exposes for configuration, and what can go wrong will save you debugging time when deployments break.
What CORS Actually Does
Cross-Origin Resource Sharing is a browser security mechanism. When JavaScript on a web page attempts to fetch data from a domain different from the one that served the page, the browser first asks the target server whether that request is permitted. This happens through an HTTP OPTIONS request, sometimes called a preflight request, which checks the server’s Access-Control-Allow-Origin and related headers.
If the server responds with headers that include the requesting domain in its allowed origins list, the browser permits the actual request to proceed. If the server does not include the appropriate headers, the browser blocks the request entirely. The data might exist on the server, the API might function correctly when called directly via tools like Postman or curl, but the browser will not allow your JavaScript code to access it.
This matters for Form.io because forms embedded in your application make API calls to the Form.io server. Loading a form definition requires fetching JSON from the Form.io API. Submitting a form posts data to the Form.io API. Every interaction between your embedded form and the Form.io backend involves a cross-origin request that the browser will block unless CORS is configured.
Configuring CORS in Project Settings
CORS configuration lives in Project Settings under the API Settings section. Navigate to your project, click Settings in the left navigation, then select the API Settings tab to find the CORS configuration field.
The configuration accepts a list of domains separated by spaces or commas. For example:
https://app.yourcompany.com, https://staging.yourcompany.com
This tells the Form.io server to include these domains in the Access-Control-Allow-Origin response header when requests originate from them. Browsers seeing this header will permit the cross-origin requests.
The default value for new projects is *, which permits requests from any origin. This works for development and testing but raises security considerations for production deployments. Any website could potentially make requests to your Form.io API if CORS is set to allow all origins.
When to Restrict CORS Domains
CORS restrictions serve as a defense-in-depth measure. They do not replace authentication and authorization, but they add a layer that prevents unauthorized websites from even attempting to interact with your forms.
Restrict CORS to specific domains when:
Your forms collect sensitive data. Even with proper authentication, restricting CORS ensures that only your authorized applications can initiate form interactions. A malicious site cannot embed your forms or submit data to your API from their domain.
You operate in a regulated environment. Security compliance requirements often mandate that you demonstrate control over which systems can access your APIs. Explicit CORS configuration provides auditable evidence of domain restrictions.
Your application architecture is stable. If you know the exact domains that will host your embedded forms, specifying them eliminates the ambiguity of wildcard access.
Leave CORS open (*) when:
You are building a platform where third parties embed your forms on their domains. If you do not control or know the embedding domains in advance, wildcard CORS is necessary for the integration to function.
You are in active development with frequently changing domains. Restrictive CORS adds friction during development when domains change or new environments spin up. Lock down CORS as part of your production deployment process.
CORS and Multi-Tenancy
Multi-tenant deployments introduce a specific constraint: all tenant projects must share the same CORS settings as the parent project. Tenants inherit CORS configuration from their parent rather than configuring their own domains independently.
This design enforces that tenants operate as part of a single application deployment rather than as separate, standalone projects. If your architecture requires different tenants to be accessible from different domain sets, you need to structure those as separate projects rather than tenants within a single parent.
The practical implication: when planning a multi-tenant deployment, identify all domains that any tenant will use, and configure those domains on the parent project. All tenants will then accept requests from that shared domain list.
Recovering from CORS Misconfiguration
A common failure mode: you configure CORS to a specific domain, mistype it, and now the Form.io portal itself cannot access your project because the portal domain is not in your allowed list. CORS will lock down the portal if the CORS domain has been misconfigured.
The recovery process requires bypassing the browser:
- Use an API tool like Postman that does not enforce CORS (CORS is a browser security feature, not a server requirement)
- Perform a
GETrequest to your project endpoint using a Project API Token in thex-tokenheader - Retrieve the project JSON, which contains the misconfigured cors value
- Modify the
corsvalue to the correct setting or back to * - Perform a
PUTrequest with the corrected JSON
This works because Postman and similar tools do not perform preflight CORS checks. They send requests directly, and the Form.io server responds regardless of origin headers. The browser-based portal enforces CORS, but API tools do not.
Detailed recovery steps are documented in the Form.io FAQ. Bookmark that page before you need it.
CORS Interaction with Stages
Project Stages enable development, staging, and production configurations within a single project. CORS settings are configured per stage, which means your development stage can have permissive CORS while your production stage restricts to specific domains.
This maps to typical deployment workflows. Development environments benefit from * CORS to simplify testing across local development servers, preview deployments, and CI/CD pipelines. Production stages should specify exactly the domains that will embed forms in the live application.
When using Protected Mode on a stage (recommended for production), the CORS configuration becomes part of the protected settings that cannot be casually modified. This prevents accidental CORS changes from breaking production deployments.
Content Security Policy Alongside CORS
CORS controls which domains can make requests to your Form.io API. Content Security Policy (CSP) controls which resources your forms can load. These are complementary but distinct security mechanisms.
CSP configuration in Project Settings allows you to specify directives like default-src, script-src, style-src, and others that control what external resources forms can reference. If your forms include custom JavaScript, external stylesheets, or embedded media, CSP configuration determines whether browsers will load those resources.
A common pattern: CORS permits your application domain to fetch forms and submit data, while CSP permits forms to load scripts and styles from your CDN. Both configurations must align with your architecture for forms to function correctly.
Debugging CORS Issues
When forms fail to load or submissions fail, CORS is a common culprit. The browser developer console displays CORS errors explicitly, typically with messages about blocked cross-origin requests or missing Access-Control-Allow-Origin headers.
First, verify that the Form.io API server is running. Append /status to your API server URL and load it in a browser. If this returns server metadata, the API is operational. If it fails, the problem is server availability, not CORS.
Second, check the actual CORS configuration in Project Settings. Typos in domain names, missing protocols (http vs https), or trailing slashes can cause CORS matching to fail. The domain must match exactly what the browser sends in the Origin header.
Third, verify you are testing from the configured domain. CORS errors are origin-specific. Testing from localhost:3000 when CORS is configured for app.yourcompany.com will fail, which is the expected behavior.
Fourth, check for intermediate proxies or load balancers that might strip or modify CORS headers. If the Form.io server sends correct headers but a proxy removes them, the browser still blocks the request.
What CORS Does Not Do
CORS is a browser security mechanism. It does not protect your API from server-to-server requests, command-line tools, or any client that ignores CORS headers. A malicious actor with a script running outside a browser can make requests to your API regardless of CORS configuration.
CORS does not replace authentication. Configure API Keys for server-to-server communication and implement proper authentication flows for user-facing applications. CORS adds a layer of defense but is not sufficient on its own.
CORS does not hide your form definitions. If a domain is in your allowed origins list, JavaScript running on that domain can fetch your form JSON. If your form structure itself is sensitive, CORS cannot protect it from users who can load your application.
CORS enforcement happens in the browser, not on the server. The Form.io server still processes and responds to requests from any origin. It includes or excludes CORS headers based on configuration, but the server does not reject requests based on origin. The browser, receiving a response without appropriate headers, blocks JavaScript access to that response.
Project-Level Configuration
CORS configuration applies at the project level. This means all forms and resources within a project share the same CORS settings. You cannot configure different CORS rules for different forms within the same project.
If your architecture requires different forms to be accessible from different domains, structure those as separate projects. Each project gets its own CORS configuration, its own API endpoint, and its own set of forms. The tradeoff is managing multiple projects rather than consolidating forms into a single project.
For most applications, project-level CORS is appropriate. Your application embeds forms from a specific Form.io project, and all those forms share the same embedding domain requirements.
Related Resources
- Project Settings Documentation covers CORS and other API settings
- FAQ includes CORS lockout recovery procedures
- Errors Documentation covers diagnosing CORS-related failures
- Authentication Support complements CORS with proper access control
- Security Compliance provides audit logging and encryption alongside domain restrictions
- Stages Documentation explains per-stage configuration including CORS
- MDN CORS Documentation provides detailed technical background on the CORS mechanism
