window.fluid. It has two members: fluid.fetch, which reaches Fluid’s APIs, and fluid.viewer(), which says who is viewing. This page covers both, who gets data back, how to handle each answer and how to declare what your scripts use.
Know how a script reaches Fluid
A site renders in a sandboxed frame. The frame has an opaque origin and no cookie. So a call from your script with the page’s ownfetch carries no session and can’t reach a Fluid API as the viewer.
The site SDK installs window.fluid in the frame instead. A call travels from the frame to Fluid’s page around it, and from there to the member API as the viewer. Fluid forwards only routes it allows. Site management routes, such as a site’s files and pages, aren’t reachable from site code.
fluid.fetch(path, init)behaves likefetch. It takes the same arguments and resolves to aResponse.fluid.viewer()says who is viewing. It returns"member"or"guest".- The page’s own
fetchstays as it is. Fluid never replaceswindow.fetch, so you can still use it to call other services. It doesn’t reach Fluid.
content_for_header, so print it in your layout’s <head>. Load your script with {{ 'site.js' | asset_url | script_tag }}, or write it inline. See Use tags and filters.
Read with fluid.fetch
Pass a path on your company’s own host. A Member API path, such as/api/member/v2026-10/me, goes as it is. Check response.ok, then read the body:
assets/site.js
pages/index.liquid
Write with fluid.fetch
Set the method ininit, as you would with fetch. Send a JSON body with a content-type header:
assets/site.js
403. A request body must be JSON, and Fluid refuses one that is too large. See Update the signed-in member for the fields.
Choose the path
- A same-host path under
/api/works. A full URL on your company’s own origin works too. - A Member API path stays. A path that starts with
/api/member/v2026-10/goes as you wrote it. - Any other
/api/path moves. Fluid sends/api/<x>to/api/member-storefront/<x>, the company host’s bridge routes. - Anything else throws. A URL on another host, or a path that doesn’t start with
/api/, throws anErrorbefore anything is sent.
Know who gets data
Only the signed-in owner of a member-owned site gets data fromfluid.fetch, and only while viewing the site published. A site whose published declaration lists no scope gets none, even for its owner.
- Sharing grants nothing. Sharing a site or making it public doesn’t change who gets data. A share holder, a company admin and a visitor never get a scope from the site’s declaration. See Share a member site.
fluid.viewer()follows that. It returns"member"only when the site’s frame can reach Fluid for the viewer. A signed-in viewer the frame can’t act for gets"guest", and a call answers with the localguestresponse below.- A company site can’t use viewer data. If you want a script to read the viewer, build a member-owned site. Build a company site from Liquid’s structure and menus only.
- A script that works for you may fail for a visitor. Test with a second member, and show a fallback when a call fails.
- A site whose owner is no longer an active member grants no scope to anyone. Its calls are refused.
- A change to the site applies at once. A take-down, an owner who is no longer active and a publish that drops a scope all refuse the next call. Fluid checks the site on every request.
Handle the Response
fluid.fetch resolves to a Response for any HTTP status, so a refusal isn’t an exception. Check response.ok first. Fluid’s own page answers some requests itself, with a JSON body that holds an error string:
Any other status comes from the API, with its own body. One refusal is about the site’s declaration. The owner of a site that published a declaration gets a
403 for a path whose scope the declaration lacks. The body names the scope:
member_site_unavailable, so a response never says whether a site exists. That covers a site that is unknown, unpublished or company-owned, a viewer who isn’t its owner and a path that no scope covers.
Read the status first, then the body:
assets/site.js
- An
Errormeans the path is on another host or outside/api/. Nothing is sent. - A
TypeErrormeans the transport failed. Fluid’s page couldn’t be reached, timed out, or answered with a network error. - An
AbortErrormeans the request’ssignalaborted.
try and catch so a rejection keeps the page usable.
Use a library
A library that accepts a customfetch works with fluid.fetch. Pass it in when you create the client. For example, with ky:
assets/site.js
XMLHttpRequest doesn’t go through fluid.fetch, so it can’t reach Fluid from a site. A library that calls the global fetch directly has the same limit.
Know the scope of a path
Each path has a scope. A scope is<family>: read or <family>: write. The method picks the mode: GET needs read, and any other method that Fluid forwards needs write. The route picks the family, so a request can’t borrow another family’s scope by changing its path.
See Show the signed-in member, Update the signed-in member, List the member’s tasks and Complete a task. The API reference lists the rest of the member API. When you aren’t sure which scope a path needs, call it from a script and run
fluid member-site lint, which names the scope the file lacks.
Some routes have no scope. A site can never reach them, and no declaration changes that. Saved payment methods and site management are examples, and so are writes to addresses and subscriptions. A call to one is refused whatever the site declares.
Declare the scopes you use
List the scope of each path your scripts call inconfig/access.json. The file is one JSON object with one key, uses:
config/access.json
fluid member-site lint checks each fluid.fetch(path, { method }) call in your scripts against the scope of that path. It warns when the file lacks the scope, when the route can never be reached by a site, and when it can’t read the call. Run it before you push. A site with no config/access.json isn’t checked this way. The file is published with the site.
Fluid enforces the published declaration. It refuses a call whose scope the file lacks with the member_site_scope_undeclared response above. Declare what a member site uses lists every scope you can declare and explains how to write, publish and check the file.
Keep scripts working with the published site
A script inassets/ is live as soon as you push it, while page content stays a draft until you publish. Write a script that still works with the published pages. For example, a script that looks for [data-greeting] and does nothing when it isn’t there works before and after the page that prints it is published.