Skip to content

Access-Control-Expose-Headers — Making Response Headers Available to JavaScript

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Access. We cover key concepts, practical examples, and best practices to help you master this topic.

The Access-Control-Expose-Headers response header tells the browser to make specific response headers available to JavaScript, overriding the default set of seven simple response headers.

What You'll Learn

  • Which headers are exposed by default
  • How to expose custom headers to JavaScript
  • Security implications of exposing headers

Why It Matters

APIs commonly use custom headers for rate limits, pagination, request tracing, and ETags. Without Expose-Headers, JavaScript cannot read these values. DodaTech's API uses custom headers for rate limit status, and the client SDK reads them via Expose-Headers.

flowchart LR
    A["Server Response"] --> B{"Header in Expose-Headers?"}
    B -->|"Yes"| C["JavaScript can read it"]
    B -->|"No"| D["Is it a simple header?"]
    D -->|"Yes"| C
    D -->|"No"| E["JavaScript cannot read it"]
    style C fill:#86efac,stroke:#16a34a
    style E fill:#fecaca,stroke:#dc2626

Code Examples

// Without Expose-Headers, custom headers are hidden
fetch('https://api.example.com/data')
  .then(response => {
    // These work - they are simple response headers
    console.log(response.headers.get('content-type'));
    console.log(response.headers.get('last-modified'));

    // These return null without Expose-Headers
    console.log(response.headers.get('x-rate-limit-remaining'));
    console.log(response.headers.get('x-total-count'));
    console.log(response.headers.get('x-request-id'));
  });
# Flask response with exposed headers
@app.route('/api/users')
def get_users():
    users = get_users_from_db()
    response = jsonify(users)
    response.headers['X-Rate-Limit-Remaining'] = '97'
    response.headers['X-Total-Count'] = '1000'
    response.headers['X-Request-ID'] = 'req-abc-123'
    response.headers['Access-Control-Expose-Headers'] = \
        'X-Rate-Limit-Remaining, X-Total-Count, X-Request-ID'
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response
// Node.js exposing headers for pagination
app.get('/api/items', (req, res) => {
  const items = getItems(req.query.page, req.query.limit);
  res.set('X-Page', req.query.page || '1');
  res.set('X-Total-Pages', Math.ceil(totalItems / limit));
  res.set('X-Total-Items', String(totalItems));
  res.set('Access-Control-Expose-Headers',
    'X-Page, X-Total-Pages, X-Total-Items');
  res.json(items);
});

// Client reads pagination headers
const response = await fetch('https://api.example.com/items');
const totalPages = response.headers.get('X-Total-Pages');
const currentPage = response.headers.get('X-Page');
# Check exposed headers
curl -I -H "Origin: https://app.example.com" \
  https://api.example.com/data | grep -i "expose-headers"

Common Mistakes

1. Confusing Expose-Headers with Allow-Headers

Allow-Headers controls request headers. Expose-Headers controls response headers.

Headers like WWW-Authenticate require explicit exposure.

3. Exposing Sensitive Headers

Avoid exposing internal headers like X-Internal-Token or X-Debug-Info.

If you set custom headers like X-CSRF-Token, expose them so JavaScript can read new values.

5. Assuming All Response Headers Are Visible by Default

Only six simple headers are visible: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma.

Practice Questions

  1. Which six response headers are visible without Expose-Headers?
  2. How do you expose the X-Total-Count header?
  3. Why would an API expose custom headers?
  4. Can you use a wildcard in Access-Control-Expose-Headers?
  5. What happens if JavaScript tries to read a non-exposed header?

Answers:

  1. Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma.
  2. Set Access-Control-Expose-Headers: X-Total-Count in the response.
  3. To allow client code to read pagination, rate limits, or request tracing information.
  4. Yes, modern browsers support the wildcard * for Expose-Headers.
  5. The browser returns null for that header.

Challenge: Refactor an API to move all relevant metadata from the response body into custom headers, expose them properly, and update the client SDK to read from headers instead of the body.

FAQ

Can I expose headers only for specific endpoints?

Yes. Expose-Headers is set per response. Different endpoints can expose different sets of headers based on what information each provides.

Do exposed headers increase security risk?

Only if you expose sensitive information. Headers like X-Internal-Nodes or X-Debug-Trace should not be exposed as they leak infrastructure details.

Is there a performance impact from exposing headers?

No. The headers are already in the response. Expose-Headers only controls JavaScript visibility, not network transmission.

Can I expose the Set-Cookie header?

Set-Cookie is not accessible via JavaScript even with Expose-Headers. Cookies are handled separately by the browser.

How does Expose-Headers interact with the wildcard and credentials?

Wildcard * for Expose-Headers cannot be used with Access-Control-Allow-Credentials: true, the same restriction as other CORS headers.

Mini Project

Build an API that returns paginated results with all metadata in headers: X-Page, X-Per-Page, X-Total, X-Total-Pages. Properly expose them, then build a JavaScript client that reads pagination state from headers and displays page navigation. Include error handling when headers change between requests.

What's Next

Explore Access-Control-Allow-Credentials to handle cookies and authentication headers cross-origin, then study Access-Control-Max-Age for preflight Caching.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro