Platform CLI
Learn
Developer docs User guide Quickstart CLI and AI agents Blog
Company
Services About Contact Links Get started

Set up user groups for advanced public page access

Updated

Raytha has no switch that makes a published page "members only". Access to public content is decided in your templates, which can check whether the visitor is signed in and which user groups they belong to. This page shows how to build that, and where it does not protect you.

How public access works in Raytha

These are the rules the server applies when a visitor requests a URL:

  • A content item, a site page or a list view is served only while it is published. Anything else answers with your 404 page. Editors with the right permission can preview an unpublished item by adding ?previewDraft=true.
  • Once an item is published, every visitor can request it, signed in or not. There is no setting on a content type, a template or a user group that changes that.
  • The template then renders the page, and the template can see who is asking through CurrentUser.

So the way to restrict what a visitor sees is to decide in the template what to print. That works well for "sign in to read the rest of this article" and "this section is for staff". It is not a vault; read Limits below before relying on it.

What a template can know about the visitor

VariableValue
CurrentUser.IsAuthenticatedtrue when the visitor is signed in.
CurrentUser.UserGroupsThe developer names of their user groups, such as premium_members.
CurrentUser.IsAdmin and CurrentUser.RolesWhether they are an administrator, and the developer names of their roles.
CurrentUser.FirstName, LastName, FullName, EmailAddressTheir profile.

Groups are read from the database on each request, so a change to someone's groups shows up on their next page load.

Example: members-only article

Goal: only people in the group with developer name premium_members can read the article body. Everyone else sees a short message and a sign-in link.

You need a user group, at least one user in it (see Public users and user groups), and permission to edit templates (Manage Templates).

  1. Open Design > Themes, open your active theme, and open the web template that renders the content type's detail page. For the built-in Posts type that is the content item detail template.
  2. Find the line that prints the body, for example {{ Target.PublishedContent.content.Text }}.
  3. Wrap it in a check, using your own field name:
{% if CurrentUser.IsAuthenticated %}
  {% if CurrentUser.UserGroups contains "premium_members" %}
    {{ Target.PublishedContent.content.Text }}
  {% else %}
    <p>This article is for premium members.</p>
  {% endif %}
{% else %}
  <p>
    <a href="{{ PathBase }}/account/login?returnUrl={{ PathBase }}/{{ Target.RoutePath }}">Sign in</a>
    to read this article.
  </p>
{% endif %}
  1. Save the template.
  2. Open the article's public URL in a private browser window, and then signed in as a user who is in the group. Impersonation helps here: open the user under People > Users and click Impersonate to see the site as them.

You should see the sign-in message when signed out, the "premium members" message for a signed-in user who is not in the group, and the full article for a member. The returnUrl sends people back to the article after they sign in. It must be a local path; absolute URLs are ignored.

Other checks you can use

{% comment %} Any signed-in visitor {% endcomment %}
{% if CurrentUser.IsAuthenticated %} ... {% endif %}

{% comment %} Either of two groups {% endcomment %}
{% if CurrentUser.UserGroups contains "staff" or CurrentUser.UserGroups contains "partners" %} ... {% endif %}

{% comment %} Administrators always see it {% endcomment %}
{% if CurrentUser.IsAdmin %} ... {% endif %}

Where to put the check

  • The detail template of a content type to protect every item of that type.
  • A single site page template or a widget template to protect a section. Widgets render through widget templates, and those can use CurrentUser as well.
  • A list view template if the list shows excerpts or fields you want to withhold. Protecting only the detail page leaves the list open.
  • A shared layout if you want one rule across a whole theme.

See How Liquid templates work in Raytha for editing templates, and Template variables for everything a template can read.

Protecting a Raytha Function

An HTTP request function is public to anyone who knows its address. If it returns data that should be private, check the visitor inside the function:

function get(query) {
    if (!CurrentUser.IsAuthenticated) {
        return new StatusCodeResult(401, "Sign in first");
    }
    const isMember = Array.from(CurrentUser.UserGroups).includes("premium_members");
    if (!isMember) {
        return new StatusCodeResult(403, "Members only");
    }
    return new JsonResult({ ok: true });
}

Unlike a template, a function can answer with a real 401 or 403 status. See How embeddable functions work in Raytha.

Limits

  • The page is still public. The server returns 200 with the page; your template just prints less. Search engines index whatever the template prints for a signed-out visitor, so keep the signed-out version free of secrets.
  • Every template that prints the data needs the check. A list view, a "latest posts" widget, a Function that builds an llms.txt and a site-wide search can all show a field you protected on the detail page.
  • Uploaded files are not protected. Anyone with a media file's address (/raytha/media-items/objectkey/...) can fetch it, whatever your templates do. Do not put private files in the media library.
  • No redirect or 403 from Liquid. A template cannot change the response status or redirect. Show a message and a link, or use a Function when you need a real status code.
  • The REST API is separate. It needs an API key tied to an administrator, so visitors cannot reach it, but anyone with a key reaches everything their roles allow regardless of your template checks.

If you need content that is unreachable without credentials, keep it out of published public pages and serve it through a Function that checks CurrentUser, or put it behind your own application.

Gotchas

  • Use the developer name, not the label. CurrentUser.UserGroups contains "Premium members" never matches. Check the exact value under People > Users > User groups.
  • Test as a signed-out visitor. Administrators browsing the site while signed in to the admin see the signed-in version.
  • Groups set by single sign-on. If your identity provider sends groups that match existing user groups, they replace manual assignments on each sign-in. See Public users and user groups.

Next steps