How to Set Up Your Own API Proxy in 15 Minutes
Build custom Squarespace integrations by setting up a free Cloudflare Workers proxy to connect any API to your site.
This is the unlock for building real, custom integrations in Squarespace. Want to pull in live inventory from a POS system? Display real-time booking availability? Show product reviews from a third-party platform? If a service has an API and auth credentials, this guide will teach you how to connect it to your Squarespace frontend.
The problem is, if you try to set this up in the Code Injection area, you'll hit CORS errors, or worse — your API keys get exposed and open for exploitation. That's a big no-no.
Integrations like this assume you have "server-side" access to your website, but Squarespace doesn't allow for that.
The fix: we simulate "server-side" access through what's called a "proxy server". A proxy server sits between your Squarespace site and the API. It keeps your credentials secret, handles all the CORS nonsense, and opens the door to custom components that go way beyond what native Squarespace blocks can do. This is how you build the kind of integrations that let you charge more and leave your clients genuinely impressed.
No doubt, this is an advanced integration and will require you to get out of your comfort zone and learn more, but career growth ain't easy.
We're using Cloudflare Workers for this. No server to maintain, no monthly bill, and it takes about 15 minutes.
What You'll Need Before We Start
Don't skip this part — get these set up first so you can follow along without hitting a wall.
1. A Cloudflare Account (Free)
Head to cloudflare.com and sign up. You don't need a paid plan. The free tier gives you 100,000 requests per day, which is way more than enough.
2. Node.js Installed
You need Node.js on your computer to use Cloudflare's command line tool. If you're not sure whether you have it:
- Open your Terminal (Mac) or Command Prompt (Windows)
- Type
node -vand press Enter - If you see a version number (like
v20.11.0), you're good - If you get an error, download it from nodejs.org — grab the LTS version
3. A Code Editor
VS Code is the standard. If you don't have it: code.visualstudio.com. Any editor works though.
4. An API You Want to Call
Have the API URL, any API keys, and the documentation handy. We'll use a generic example, but this works with basically any REST API.
Step 1: Create Your Project
Open your terminal and run:
npm create cloudflare@latest my-api-proxyIt'll ask you a few questions:
- What type of application? → Select "Hello World" Worker
- TypeScript? → Select No (we're keeping it simple)
- Deploy now? → Select No (we'll deploy after we write our code)
Now move into your project folder:
cd my-api-proxyStep 2: Write the Proxy
Open the project in your code editor. Find the file at src/index.js (or worker.js depending on your version). Delete everything in there and replace it with this:
export default {
async fetch(request, env) {
// --------------------
// CONFIGURATION
// --------------------
// The base URL of the API you want to call
const API_BASE_URL = "https://api.example.com";
// Any headers/keys the API needs (these stay secret on the server)
const API_HEADERS = {
"Authorization": "Bearer YOUR_API_KEY_HERE",
"Content-Type": "application/json",
};
// Which domains are allowed to use this proxy
const ALLOWED_ORIGINS = [
"https://yourwebsite.com",
"https://www.yourwebsite.com",
"http://localhost:3000", // for local development
];
// --------------------
// DON'T EDIT BELOW THIS LINE (unless you want to)
// --------------------
const origin = request.headers.get("Origin") || "";
// Check if the request is coming from an allowed domain
if (!ALLOWED_ORIGINS.includes(origin)) {
return new Response("Not allowed", { status: 403 });
}
// CORS headers we'll attach to every response
const corsHeaders = {
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
};
// Handle preflight requests (the browser's "can I do this?" check)
if (request.method === "OPTIONS") {
return new Response(null, { headers: corsHeaders });
}
// Build the URL to forward to
// If your proxy is at: https://my-proxy.workers.dev/v1/users
// This forwards to: https://api.example.com/v1/users
const url = new URL(request.url);
const targetUrl = API_BASE_URL + url.pathname + url.search;
try {
// Forward the request to the real API
const apiResponse = await fetch(targetUrl, {
method: request.method,
headers: {
...API_HEADERS,
// Pass along the content-type from the original request
"Content-Type": request.headers.get("Content-Type") || "application/json",
},
// Forward the body for POST/PUT requests
body: ["GET", "HEAD"].includes(request.method) ? null : request.body,
});
// Send the API's response back with CORS headers attached
const responseBody = await apiResponse.text();
return new Response(responseBody, {
status: apiResponse.status,
headers: {
...corsHeaders,
"Content-Type": apiResponse.headers.get("Content-Type") || "application/json",
},
});
} catch (error) {
return new Response(JSON.stringify({ error: "Proxy request failed" }), {
status: 500,
headers: { ...corsHeaders, "Content-Type": "application/json" },
});
}
},
};Step 3: Configure It For Your API
The only part you need to change is the CONFIGURATION section at the top:
-
API_BASE_URL— Change this to the base URL of whatever API you're calling. Just the base, no trailing slash. -
API_HEADERS— Add whatever authentication the API needs. This is the magic part: these keys live on Cloudflare's servers, not in your frontend code. Your website visitors will never see them. -
ALLOWED_ORIGINS— List every domain that should be allowed to use this proxy. Keephttp://localhost:3000in there while you're developing. Remove it before going live if you want to lock it down.
Step 4: Test It Locally
Run this in your terminal:
npx wrangler devThis starts a local version of your proxy (usually at http://localhost:8787). You can test it by opening a new terminal and running:
curl http://localhost:8787/any/api/endpointOr from your browser's console on one of your allowed domains:
const response = await fetch("http://localhost:8787/v1/whatever-endpoint");
const data = await response.json();
console.log(data);Step 5: Deploy It (For Real)
When it's working locally, deploy to Cloudflare:
npx wrangler deployThe first time, it'll ask you to log in to your Cloudflare account in the browser. After that, it'll give you a URL like:
https://my-api-proxy.YOUR-SUBDOMAIN.workers.dev
That's your proxy URL. Done. It's live.
Step 6: Use It In Your Code
Wherever you were calling the API directly, replace the base URL with your proxy URL:
// Before (this gets blocked by CORS)
const response = await fetch("https://api.example.com/v1/data");
// After (goes through your proxy, CORS handled)
const response = await fetch("https://my-api-proxy.YOUR-SUBDOMAIN.workers.dev/v1/data");That's literally it. Your proxy forwards the request, injects your API key, and sends back the response with the right CORS headers.
What Did We Actually Build?
Here's what's happening in plain English:
- Your Squarespace site sends a request to your proxy URL
- The proxy checks: "Is this request coming from an allowed domain?" If not, it blocks it.
- If allowed, the proxy forwards the request to the real API and attaches your secret API key
- The API responds to the proxy
- The proxy sends the response back to your site with CORS headers so the browser doesn't freak out
Your API key never touches the browser. Your allowed domains are enforced. The browser is happy. You're happy.
This is the foundation for any custom Squarespace integration — booking widgets, live inventory displays, CRM lookups, review feeds, payment flows, you name it. Anything with an API is now fair game.
Quick Troubleshooting
"I'm getting a 403 error"
Your domain isn't in the ALLOWED_ORIGINS list. Make sure it matches exactly — https:// vs http://, www vs no www, and trailing slashes matter.
"I'm getting CORS errors still"
Make sure ALLOWED_ORIGINS includes the exact origin your browser is sending. Check the browser console → Network tab → look at the Origin header on the failed request.
"It works locally but not deployed"
Did you update ALLOWED_ORIGINS to include your production domain? localhost won't work in production.
"The API returns HTML instead of JSON"
Double-check your API_BASE_URL. You might have a typo or the API path structure might be different than you expect.
Free Tier Limits (Plenty for Most People)
- 100,000 requests per day — that's roughly 1 request per second, all day
- 10ms CPU time per request — proxy requests are fast, you won't hit this
- KV Storage (if you want to get fancy later) — 100,000 reads/day, 1,000 writes/day
If you outgrow the free tier, the paid plan is $5/month for 10 million requests.
Next Steps (If You Want to Get Fancy)
- Use Environment Variables for your API key instead of hardcoding it:
npx wrangler secret put API_KEYthen access it asenv.API_KEYin your code - Add Rate Limiting to prevent abuse
- Add Caching for GET requests that don't change often (saves API calls)
- Add Logging to track usage
But honestly? The basic version above handles 90% of use cases. Ship it, move on, build the thing you actually care about.
