ToolJet makes it very easy to wire up APIs, databases, and internal tools fast. Then CORS shows up and ruins your afternoon.

If you are building ToolJet apps that call APIs from the browser, CORS is one of those constraints you cannot brute-force away. You either design around it, proxy around it, or configure it correctly on the server. There is no frontend-only hack that “disables CORS” in production. If somebody suggests a browser extension, close the tab.

This guide compares the common ways to deal with CORS in ToolJet applications, with the pros, cons, and where each approach actually makes sense.

What CORS means for ToolJet

CORS, or Cross-Origin Resource Sharing, is the browser deciding whether JavaScript on one origin can read responses from another origin.

If your ToolJet app runs on:

https://tooljet.example.com

and it tries to call:

https://api.example.com

the browser checks the response headers from api.example.com. If the API does not explicitly allow your ToolJet origin, the request may still hit the server, but the browser blocks your app from reading the response.

The two headers you will care about most are:

Access-Control-Allow-Origin: https://tooljet.example.com
Access-Control-Allow-Credentials: true

Or, for public APIs without cookies or auth tied to browser credentials:

Access-Control-Allow-Origin: *

A real example from api.github.com:

access-control-allow-origin: *
access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset, Warning

That is why browser-based requests to GitHub’s API are usually painless: GitHub explicitly allows any origin and exposes useful headers to frontend code.

Your main options in ToolJet

There are really four patterns worth comparing:

  1. Call the API directly from the browser
  2. Use ToolJet’s backend/server-side connector path
  3. Put a reverse proxy in front of the API
  4. Change the API server to send the right CORS headers

Option 1: Direct browser requests from ToolJet

This is the simplest setup. Your ToolJet app calls the target API directly.

Best when

  • The API already supports CORS
  • The API is public or token-based
  • You do not need to hide secrets in the browser
  • You want the fewest moving parts

Pros

  • Fastest to set up
  • No extra proxy infrastructure
  • Lower latency than bouncing through another service
  • Easy to debug in browser devtools

Cons

  • Breaks immediately if the API does not allow your origin
  • Exposes request details to the browser
  • You cannot safely keep sensitive API secrets in client-side code
  • Preflight requests can add noise and latency

Example

A simple fetch from a ToolJet JavaScript query or frontend context:

const res = await fetch("https://api.github.com/repos/tooljet/tooljet", {
  headers: {
    "Accept": "application/vnd.github+json"
  }
});

if (!res.ok) {
  throw new Error(`GitHub API failed: ${res.status}`);
}

const data = await res.json();
return {
  stars: data.stargazers_count,
  forks: data.forks_count
};
```text

This works because GitHub sends:

access-control-allow-origin: *


And if you need rate limit details in the browser, GitHub also exposes them via:

access-control-expose-headers: … X-RateLimit-Limit, X-RateLimit-Remaining …


Without `Access-Control-Expose-Headers`, your frontend code would not be able to read many custom response headers even if the request itself succeeded.

### My take

Use direct browser requests only when the API is intentionally browser-friendly. Public APIs like GitHub are fine. Internal APIs with cookies, private tokens, or weird legacy auth usually are not.

## Option 2: ToolJet server-side requests

This is usually the most practical fix. Instead of the browser calling the API, ToolJet’s backend makes the request. Server-to-server requests are not blocked by browser CORS rules.

### Best when

- The target API does not support CORS
- You need to keep API keys private
- You are calling internal services
- You want more control over auth and transformation

### Pros

- Bypasses browser CORS entirely
- Keeps secrets off the client
- Easier to integrate with internal networks
- Better place for request signing, retries, and logging

### Cons

- More app-side configuration
- Harder to inspect than raw browser traffic
- Can increase backend load
- You still need to think about auth, rate limiting, and data exposure

### Example flow

Browser:

ToolJet UI -> ToolJet backend -> target API


Instead of this:

ToolJet UI -> target API


If you are building a ToolJet datasource or server-side query, the backend can call the API with a secret header:

```javascript
const res = await fetch("https://internal-api.example.com/reports", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.REPORTS_API_TOKEN}`,
    "Accept": "application/json"
  }
});

if (!res.ok) {
  throw new Error(`Reports API failed: ${res.status}`);
}

return await res.json();

No browser CORS policy applies here because this request is happening on the server.

My take

If you are building serious internal tools, this is the default I would reach for first. It is cleaner, safer, and avoids a lot of fake “frontend problem” debugging that is really just browser policy doing its job.

Option 3: Reverse proxy in front of the API

If you cannot change the target API and do not want every ToolJet request to be custom server logic, a reverse proxy is a solid middle ground.

You place something like Nginx, Apache, or your edge layer in front of the API and make it return the CORS headers ToolJet needs.

Best when

  • You control infrastructure but not the app code
  • Multiple frontend apps need the same CORS policy
  • You want centralized policy enforcement

Pros

  • Centralized control
  • Works with existing APIs
  • Can normalize headers across services
  • Good place for auth, caching, and rate limiting

Cons

  • Extra infrastructure to maintain
  • Easy to misconfigure badly
  • Can mask underlying API issues
  • Debugging proxy vs upstream behavior can get annoying fast

Nginx example

location /api/ {
    proxy_pass https://upstream-api.example.com/;

    add_header Access-Control-Allow-Origin "https://tooljet.example.com" always;
    add_header Access-Control-Allow-Credentials "true" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

    if ($request_method = OPTIONS) {
        return 204;
    }
}

That handles both the actual request and the browser preflight.

My take

A reverse proxy is great when you need consistency across teams. It is dangerous when people cargo-cult Access-Control-Allow-Origin: * onto everything, including authenticated endpoints. That is how sloppy internal tools become security incidents.

Option 4: Fix the API itself

If you own the API, this is the cleanest long-term answer.

Best when

  • You control the backend
  • The API is meant to be called by browsers
  • You want explicit, maintainable behavior

Pros

  • Correct at the source
  • No proxy band-aids
  • Easier for future consumers
  • Fine-grained origin control

Cons

  • Requires backend changes
  • Different frameworks handle CORS differently
  • Easy to get credential rules wrong

Express example

import express from "express";
import cors from "cors";

const app = express();

app.use(cors({
  origin: "https://tooljet.example.com",
  credentials: true,
  exposedHeaders: ["ETag", "Link", "X-RateLimit-Remaining"]
}));

app.get("/data", (req, res) => {
  res.json({ ok: true });
});

app.listen(3000);
```text

If you need cookies or authenticated browser sessions, do not use `*` for `Access-Control-Allow-Origin`. Browsers reject credentialed cross-origin requests when the allow-origin value is wildcard.

That means this is invalid for credentialed requests:

Access-Control-Allow-Origin: * Access-Control-Allow-Credentials: true


You need a specific origin:

Access-Control-Allow-Origin: https://tooljet.example.com Access-Control-Allow-Credentials: true


### My take

If the API is yours, fix it there. Proxies are useful. Permanent proxy dependence for a basic CORS policy is usually a smell.

## Comparison table

### Direct browser requests

**Pros**
- Simple
- Low latency
- Great for public APIs

**Cons**
- Requires API CORS support
- Secrets exposed to client
- Limited control

### ToolJet server-side requests

**Pros**
- No browser CORS issues
- Safer for secrets
- Better for internal APIs

**Cons**
- More backend setup
- Backend becomes dependency path

### Reverse proxy

**Pros**
- Centralized policy
- Works without changing upstream API
- Reusable across apps

**Cons**
- Extra ops burden
- Misconfiguration risk

### Fix the API

**Pros**
- Correct long-term solution
- Explicit and maintainable

**Cons**
- Requires backend ownership
- Takes more coordination

## Common ToolJet CORS failures

### Preflight fails

If the browser sends an `OPTIONS` request before the real request, your API or proxy must answer it correctly.

Typical reasons it fails:

- `Access-Control-Allow-Methods` missing the requested method
- `Access-Control-Allow-Headers` missing `Authorization` or `Content-Type`
- `OPTIONS` not handled at all

### Credentials mismatch

If ToolJet sends cookies or auth tied to browser credentials:

- `Access-Control-Allow-Origin` must be a specific origin
- `Access-Control-Allow-Credentials: true` must be present

### Response headers not readable

Your request works, but JavaScript cannot read custom headers like rate limits or pagination.

Fix that with:

Access-Control-Expose-Headers: ETag, Link, X-RateLimit-Remaining


GitHub is a good real-world model here. It exposes a long list of useful headers instead of forcing frontend apps to guess.

## Security opinion: do not treat CORS as auth

CORS is a browser read policy, not an access control system.

A server that responds to anyone on the internet is still public, even if only one origin is allowed to read it from browser JavaScript. Non-browser clients can still hit it directly unless you enforce real authentication and authorization.

If you are tightening broader browser-side protections beyond CORS, look at security headers like CSP too. The official references are good, and if you want a practical CSP-focused guide, [CSP Guide](https://csp-guide.com) is useful.

## What I would choose

For most ToolJet apps:

- **Public third-party API with good CORS:** call it directly
- **Private API or internal service:** use ToolJet server-side requests
- **Shared enterprise environment with many frontends:** add a reverse proxy
- **You own the backend:** fix CORS on the API itself

That is the whole game. The trick is not “how do I disable CORS,” because you do not. The trick is choosing the request path that matches your security model and your ownership boundaries. Once you do that, CORS stops being mysterious and starts being just another deployment detail.