Cross-Origin Resource Sharing (CORS) is a security mechanism that allows a web application running at one origin to access resources from a different origin. Without CORS, browsers block cross-origin HTTP requests by default as a security measure.

You’ll need to configure CORS when:

  • Local Development: You’re developing locally and your client runs on a different port than your actor service
  • Different Domain: Your frontend application is hosted on a different domain than your actor service

Registry-Level CORS

Configure CORS directly in your registry setup for simple cases:

import { setup } from "@rivetkit/actor";
import counter from "./counter";

const registry = setup({
  use: { counter },
  cors: { 
    origin: "https://yourdomain.com",
    credentials: true 
  }
});

This approach works well for basic setups but has limitations for complex scenarios.

For production applications, configure CORS at the router level for maximum control:

import { registry } from "./registry";
import { Hono } from "hono";
import { cors } from "hono/cors";
import { ALLOWED_PUBLIC_HEADERS } from "@rivetkit/actor";

const { serve } = registry.createServer();
const app = new Hono();

app.use("*", cors({
  origin: ["http://localhost:3000", "https://myapp.com"],
  allowHeaders: [
    "Authorization", 
    "Content-Type",
    ...ALLOWED_PUBLIC_HEADERS
  ],
  allowMethods: ["POST", "GET", "OPTIONS"],
  exposeHeaders: ["Content-Length"],
  maxAge: 600,
  credentials: true,
}));

serve(app);

Required Headers for RivetKit

RivetKit requires specific headers for communication. Always include ALLOWED_PUBLIC_HEADERS:

import { ALLOWED_PUBLIC_HEADERS } from "@rivetkit/actor";

// ALLOWED_PUBLIC_HEADERS includes:
// - "Content-Type"
// - "User-Agent" 
// - "X-RivetKit-Query"
// - "X-RivetKit-Encoding"
// - "X-RivetKit-Conn-Params"
// - "X-RivetKit-Actor"
// - "X-RivetKit-Conn"
// - "X-RivetKit-Conn-Token"

const corsConfig = {
  allowHeaders: [
    "Authorization",           // For your auth tokens
    ...ALLOWED_PUBLIC_HEADERS  // Required RivetKit headers
  ]
};

Without ALLOWED_PUBLIC_HEADERS, RivetKit clients won’t be able to communicate with your actors from the browser.

Framework-Specific Examples

Express.js

import express from "express";
import cors from "cors";
import { ALLOWED_PUBLIC_HEADERS } from "@rivetkit/actor";
import { registry } from "./registry";

const { handler } = registry.createServer();
const app = express();

app.use(cors({
  origin: ["http://localhost:3000", "https://myapp.com"],
  allowedHeaders: [
    "Authorization",
    "Content-Type", 
    ...ALLOWED_PUBLIC_HEADERS
  ],
  credentials: true,
}));

app.use("/registry", handler);
app.listen(8080);

Elysia

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { ALLOWED_PUBLIC_HEADERS } from "@rivetkit/actor";
import { registry } from "./registry";

const { handler } = registry.createServer();

const app = new Elysia()
  .use(cors({
    origin: ["http://localhost:3000", "https://myapp.com"],
    allowedHeaders: [
      "Authorization",
      "Content-Type",
      ...ALLOWED_PUBLIC_HEADERS
    ],
    credentials: true,
  }))
  .mount("/registry", handler)
  .listen(8080);

Configuration Options

origin

string | string[] | (origin: string) => boolean | string

Specifies which domains can access your resources:

// Single domain
origin: "https://example.com"

// Multiple domains
origin: ["https://app.com", "https://admin.com"]

// Dynamic validation
origin: (origin) => {
  return origin?.endsWith('.example.com') ? origin : false;
}

// All domains (not recommended for production)
origin: "*"

allowMethods

string[]

HTTP methods clients are allowed to use:

allowMethods: ["GET", "POST", "OPTIONS"]  // Common for RivetKit

allowHeaders

string[]

Headers that clients can send in requests:

allowHeaders: [
  "Authorization",           // Your auth headers
  "Content-Type",           // Standard content type
  "X-API-Key",              // Custom API key header
  ...ALLOWED_PUBLIC_HEADERS // Required RivetKit headers
]

credentials

boolean

Whether to allow credentials (cookies, auth headers):

credentials: true  // Required for authentication

When credentials: true, you cannot use origin: "*". Specify exact origins instead.

maxAge

number

How long browsers cache CORS preflight responses (in seconds):

maxAge: 600  // Cache for 10 minutes

exposeHeaders

string[]

Server headers that browsers can access:

exposeHeaders: ["Content-Length", "X-Request-Id"]

Development vs Production

Development Setup

For local development, allow localhost origins:

const isDev = process.env.NODE_ENV !== "production";

const corsConfig = {
  origin: isDev 
    ? ["http://localhost:3000", "http://localhost:5173"] 
    : ["https://myapp.com"],
  allowHeaders: ["Authorization", ...ALLOWED_PUBLIC_HEADERS],
  credentials: true,
};

Production Setup

For production, be restrictive with origins:

const corsConfig = {
  origin: [
    "https://myapp.com",
    "https://www.myapp.com",
    "https://admin.myapp.com"
  ],
  allowHeaders: ["Authorization", ...ALLOWED_PUBLIC_HEADERS],
  credentials: true,
  maxAge: 3600, // Cache for 1 hour
};

Troubleshooting

Common CORS Errors

“Access to fetch blocked by CORS policy”

  • Add your frontend’s origin to the origin list
  • Ensure ALLOWED_PUBLIC_HEADERS are included in allowHeaders

“Request header not allowed”

  • Add the missing header to allowHeaders
  • Include ALLOWED_PUBLIC_HEADERS in your configuration

“Credentials mode mismatch”

  • Set credentials: true in CORS config
  • Cannot use origin: "*" with credentials

Debug CORS Issues

Enable CORS logging to debug issues:

// Log CORS requests in development
if (process.env.NODE_ENV === "development") {
  app.use("*", async (c, next) => {
    console.log("CORS request:", {
      origin: c.req.header("Origin"),
      method: c.req.method,
      headers: Object.fromEntries(c.req.headers.entries())
    });
    await next();
  });
}

Best Practices

  1. Use Router-Level CORS: More flexible than registry-level configuration
  2. Include ALLOWED_PUBLIC_HEADERS: Required for RivetKit communication
  3. Specify Exact Origins: Avoid wildcards in production
  4. Enable Credentials: Needed for authentication
  5. Cache Preflight Requests: Use appropriate maxAge values
  6. Environment-Specific Config: Different settings for dev/prod