Skip to main content
A member site’s JavaScript reads the signed-in owner’s data and acts as the owner through window.fluid.fetch. Each path it calls needs a scope, such as profile: read. A site says which scopes its scripts use in one file, config/access.json. Because a site is code its owner wrote, Fluid doesn’t hand that code everything. Once the site publishes a declaration, Fluid lets the owner’s code reach only what the file lists. Lint checks your scripts against the file, and Fluid publishes it with the site. This page lists every scope a site can declare. It also covers how to write the file, how to check it before you publish and where the published declaration is reported. To see the calls themselves, read Read viewer data in site scripts.
Member sites must be turned on for your company by Fluid. To ask for them, contact support. This page is about member sites v2, which replaces theme member sites. Theme member sites are deprecated and will be removed.

Know how a declaration works

  • A declaration limits the owner’s code. Each entry is a scope, such as tasks: write. Once the site has published a declaration, it narrows what the owner’s code can reach to the scopes it lists. Fluid refuses a call whose scope the file lacks, so a call you forgot to declare fails for you first.
  • A site with no declaration behaves as it did before. The owner of a member’s site can call every route fluid.fetch allows. Every other viewer gets no data from the site’s scripts, with or without a declaration.
  • A declaration only narrows. The routes that no site can reach, and the routes a site can read but not change, stay that way for every declaration. A guest gets nothing either way.
  • Lint checks your scripts against it. fluid member-site lint checks each fluid.fetch(path, { method }) call against the scope of that path. A site with no file isn’t checked this way.
  • A declaration is a draft until you publish. Fluid publishes the declaration together with the site’s settings. A site’s access field reports the declaration the site last published, never the draft.
  • Only member-owned sites have one. A company site takes no declaration. The file API refuses config/access.json on a company site, and a company site never lists it.
  • Nobody else gains anything. Sharing a site, sharing it again and a site’s declaration never give a share holder, an admin or a visitor a scope. Only the signed-in owner of a member-owned site gets data from the site’s scripts. See Know who gets data.
  • Narrowing is immediate. 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.

Write a declaration

config/access.json is one JSON object with one key, uses. It holds a list of scope strings.
config/access.json
This declaration fits a site whose scripts read the member, list tasks, and complete them:
It lets the owner’s code read the signed-in member and their tasks, and complete a task. It asks for nothing else. A call to change the member’s details, such as fluid.fetch("/api/member/v2026-10/me", { method: "PATCH", body }), needs profile: write. This file doesn’t list it, so Fluid refuses the call with a 403 that names the scope.
  • A scope is <family>: read or <family>: write, such as tasks: read. write doesn’t include read, so a site that does both declares both.
  • The file is at most 4 KiB and lists at most 64 entries, with no entry twice.
  • Fluid refuses a file with an unknown key, a repeated key, an entry that isn’t a string, an unknown field, family or mode, or session: read.
  • Don’t declare session: read. Lint reports it as an error.
  • { "uses": [] } declares nothing. That is not the same as having no file. With no file, the site behaves as it did before. With an empty list, the site asks for nothing, so its scripts reach no data. Lint doesn’t check calls when there is no file, and it warns on every call when the list is empty.
  • A file written earlier may still list entries such as member.email. Fluid accepts them so that file stays valid, but your scripts don’t need them. To read the member in a script, declare profile: read and call fluid.fetch("/api/member/v2026-10/me").
  • A field entry grants no call. A declaration of member fields alone lists no scope, so the site’s scripts reach no data. The one field Fluid reads is member.full_name. The session answer, GET /api/member-storefront/session, carries the owner’s display_name only for a site with no declaration or one that lists member.full_name. Otherwise it is blank.

Declare scopes

A scope names a family of the member API and a mode. These are the families a site can declare. Use read for calls that read and write for calls that change something.
  • The mode comes from the request. A GET request needs read. Any other method that Fluid forwards needs write.
  • The family comes from the route. Fluid takes it from the route that serves the request, so a request can’t borrow another family’s scope by changing its path.
  • A route with no family is refused. A site that has a declaration can’t call a route that appears in no family above.
  • Some routes are read only. addresses and subscriptions are read only: a write is refused for every declaration. Push devices under notifications are read only too.
  • session: read is never declared. Every bridged call needs the viewer’s session first, so Fluid grants session: read to any declaration that holds at least one other scope. A declaration with no scope, member fields alone included, gets no session. The owner’s frame gets no handshake and a blank session, so the site’s scripts reach no data.
  • A declaration never opens a route that the member API keeps off the browser. Saved payment methods, the member’s companies, the member API’s Exigo routes, and site management and shares stay refused for every site.
The path decides the scope. Read viewer data in site scripts shows the scope of common paths. 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: it names the scope the file lacks. A native widget route, such as the team tree, needs widgets: read, not a team scope.

Publish, change and remove a declaration

Write the file with the file API, or with the CLI by editing the pulled folder and pushing it. A declaration you write is a draft until the site publishes.
  • Publish. Publishing the site publishes the declaration together with the settings.
  • Change. Edit the file, push it and publish again.
  • Remove. Delete config/access.json and push. That clears the draft declaration only: the published one stays until you publish the site. Publishing a site with no draft declaration removes the one it had published, and the site returns to the behavior it had with no declaration.
  • Copy. A member’s copy of a member’s site starts with the original’s published declaration as its own draft. A company admin’s copy of a member’s site starts with none, because a company site takes no declaration. After that, changes to either site never change the other.
  • Company sites. The file API refuses the path on a company site, and fluid member-site push leaves it alone.
See Write a site file and Publish a site for the API, and the CLI page for pull, push and publish. Publish, restore, copy and take down a member site covers what a restore does to a declaration.

Check a declaration

The CLI carries the file with the other site files:
  • pull writes it. pull writes config/access.json into the folder when the site has a draft declaration. When the site no longer has one, pull deletes your local copy if you haven’t edited it since the last pull or push. A copy you edited stays, unless you pull with --force.
  • push sends it. push sends the file with the other site files, and a push with no edit sends nothing for it. push refuses an invalid declaration before it sends any request.
Run fluid member-site lint in the folder before you push. It sends no request.
  • An invalid file is an error. Lint reports each problem the file API would refuse: not valid JSON, a key other than uses or a repeated key, an entry that isn’t a string or appears twice, an unknown field, family or mode, session: read, more than 64 entries, and a file over 4 KiB.
  • A fluid.fetch call the declaration lacks is a warning. When the file is valid, lint checks each fluid.fetch(path, { method }) call in a script against the scope of that path. It warns when the declaration doesn’t list the scope, such as a PATCH to /api/member/v2026-10/me without profile: write. It also warns when the route is one a site can never reach, and when it can’t read the call, for example because the path or method comes from a variable.
  • A Liquid file that reads member.<field> is a warning. Lint warns whatever the declaration holds, because member data is leaving member site Liquid. Read it in a script with fluid.fetch("/api/member/v2026-10/me"), which needs profile: read.
A warning doesn’t fail the command.

See what a site declares

The site’s payload in the member API and the company API carries the published declaration as access, with declared and the list of uses. declared is false for a site that has published no declaration, and for a company site. Menu entries in the member’s account menu carry it too. The summary reports the declaration the site published. See Show a site and Get a member site.

Handle a refusal

The owner of a site that published a declaration gets a 403 for a call whose scope the file lacks. The body’s details.error is member_site_scope_undeclared, and details.scope names the missing scope, such as profile: write. fluid.fetch resolves to that Response, so read it and add the scope. See Handle the Response. Only the owner sees the scope named. Every other refusal keeps one body, member_site_unavailable, whether the site is unknown, unpublished or company-owned, the viewer isn’t its owner, or no scope covers the path. A guest gets the local 401 with the error guest.

Fix a declaration