METHOD FILM/ DocumentationMembership platform · v1.0
Documentation / Introduction
MEMBERSHIP PLATFORM

Documentation

Build, configure, and operate the Method Film membership platform.

WORKING DRAFTVersion 1.0 · Reviewed October 28, 2026

Read this first. This manual reflects available source excerpts and test results. Items marked VERIFY IN REPOSITORY need confirmation against deployed source. Core functionality is deployed; selected integrations and regression checks remain open.

Find your starting point

Audience: Site builders, Webflow editors, administrators, developers, and operators. The source code and deployed configuration take precedence over this working manual.

1. Platform overview and architecture

1.1 What this platform does

This system adds invitation-only user accounts, authenticated member sessions, course ownership checks and employee administration to a Webflow website. Webflow supplies the public pages and page elements; the Cloudflare Worker owns authentication, invitations, account records and course entitlements. Email is delivered by Resend.

Primary capabilities:

  • Create user accounts from valid, single-use invitation links.
  • Sign in and out using email and password.
  • Request and complete password resets.
  • Display different content for guests and signed-in members.
  • Grant and revoke access to individual courses.
  • Gate whole course pages and mark course-specific elements.
  • Provide an employee administration interface for members, courses and invitations.
  • Suspend or restore member accounts, request verified email changes, and delete member accounts.
  • Create, resend, correct and remove unused invitations.

1.2 Components

ComponentTechnologyResponsibility
WebsiteWebflowPages, forms, navigation, custom attributes and embedded scripts
Browser integrationsrc/sdk/members.jsSession discovery, page guards, form handling, visibility attributes
Membership APICloudflare Worker + HonoHTTP endpoints, administrator actions and enrollment workflows
AuthenticationBetter AuthCredentials, session management, recovery and verification
Persistent storageCloudflare D1 SQLiteUsers, sessions, invitations, course entitlements
SQL mappingDrizzle ORMDatabase schema and Better Auth adapter
Email deliveryResendInvitation and authentication-related messages
Admin interfaceWebflow Code EmbedMember/invitation tables, modals and management actions

1.3 Request flow

TEXT · EXAMPLE
Visitor opens a Webflow page
        |
        v
Webflow loads membership browser SDK
        |
        +--> GET /api/auth/get-session (cookie included)
        |
        +--> GET /api/enrollment/access (course keys)
        |
        v
SDK updates member/guest UI and applies page guard
        |
        v
For account changes and enrollment: browser calls Worker API
        |
        +--> Better Auth and Hono handlers
        +--> D1 database
        +--> Resend when an email is required

Critical: Hiding an element is a presentation technique. It does not protect confidential page contents already delivered in Webflow HTML. The backend must remain the authority for course entitlement, and truly restricted files or media require server-side authorization or a separate protected delivery system.

1.4 Current Method Film endpoints

  • Website: https://www.methodfilm.com
  • Membership API: https://members.methodfilm.com
  • Login: https://www.methodfilm.com/login
  • Activation: https://www.methodfilm.com/activate
  • Account recovery page: /reset-password (observed in SDK)
  • Access-denied page: /access-denied (observed in SDK)
  • Employee dashboard: Webflow /admin page (additional Webflow password protection is used)

For a new installation, replace these with site-specific configuration. Do not copy production URLs into reusable source without explicitly updating the installation settings.

2. Project structure and ownership of code

2.1 Existing local project

Method Film's project root on the development Mac:

TEXT · EXAMPLE
~/webflow-memberships/membership-api/

Observed/project-confirmed structure:

TEXT · EXAMPLE
membership-api/
├── src/
│   ├── index.ts                  # Hono routes: auth, enrollment, administration
│   ├── auth.ts                   # Better Auth, mail callbacks and AuthEnv
│   ├── config.ts                 # Allowed origins, text escaping, configuration helpers
│   ├── email.ts                  # Resend email delivery wrapper
│   ├── db/
│   │   └── schema.ts             # Drizzle tables
│   ├── admin/
│   │   └── permissions.ts        # Require active authenticated administrator
│   ├── enrollment/
│   │   └── invitations.ts        # Token creation, hashing and invitation email
│   └── sdk/
│       └── members.js            # Browser SDK used by Webflow
├── migrations/
│   ├── 0002_create_enrollment_invitations_table.sql
│   ├── 0003_create_course_entitlements.sql
│   ├── 0004_add_user_role.sql
│   ├── 0005_add_account_disabled.sql
│   └── 0006_member_email_changes.sql
├── template/
│   └── bootstrap.sql             # Reusable-installation bootstrap schema
├── public/                       # Worker-served static assets
└── wrangler.dev.jsonc            # Current reference Worker configuration

Other files likely exist (package manifest, lock file, generated migration artifacts). Use the repository as the authoritative inventory. The exact reusable template packaging is not yet synchronized/verified with the latest member administration changes.

2.2 What goes where

LocationPut this thereDo not put this there
src/index.tsAPI routes and authorization logicWebflow page styling
src/auth.tsBetter Auth setup, auth hooks, typed settingsWebflow element attribute rules
src/sdk/members.jsBrowser behavior common to site pagesPrivate invitation API key
Webflow site-wide headGlobal configuration, early CSS needed to prevent protected-content flashDatabase credentials or other secrets
Webflow site-wide footerOne membership SDK script reference/initializationDuplicated copies of the SDK on every page
Webflow page headPage-specific gating styles and safe per-page metadata/configGlobal script repeated unnecessarily
Webflow element attributesDeclarative selectors: who sees an element, which form/field it representsPasswords or API keys
Webflow admin Code EmbedEmployee UI HTML/CSS/browser JSDatabase secrets, privileged service API key
Worker secretsBETTER_AUTH_SECRET, RESEND_API_KEY, INVITATION_API_KEYPublic website code

3. Configuration reference: URLs, secrets and keys

3.1 Public/site-specific Worker settings

The reference Worker uses these names in wrangler.dev.jsonc or equivalent deployment settings:

NameExample / meaningRequired?
SITE_NAMEMethod Film; used in mail subjects/contentYes
SITE_URLhttps://www.methodfilm.com; canonical Webflow originYes
BETTER_AUTH_URLhttps://members.methodfilm.com; backend/auth originYes
LOGIN_URLhttps://www.methodfilm.com/login; destination after verified email changeYes for latest implementation
ACTIVATION_URLhttps://www.methodfilm.com/activate; receives invitation ?token=...Yes
ALLOWED_ORIGINSComma-separated approved Webflow domains, including staging if usedYes
EMAIL_FROMVerified sender, e.g. Method Film <accounts@send.methodfilm.com>Yes
INVITATION_EXPIRY_DAYSNumber of days, validated 1–30Yes
GOOGLE_LOGIN_MODEdisabled in the current reference installationCurrent install
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRETUsed only if Google login is enabledConditional

Important: LOGIN_URL is now configurable in the backend. The inspected src/sdk/members.js still contains literal /login, /access-denied, /activate, and /reset-password paths. Those should be refactored into a shared, site-specific browser configuration before claiming the entire front end is fully reusable.

3.2 Private Worker secrets

SecretPurposeExposure rule
BETTER_AUTH_SECRETSigns/protects authentication stateNever embed in Webflow
RESEND_API_KEYAuthorizes sending emails via ResendNever embed in Webflow
INVITATION_API_KEYAuthorizes a privileged invitation-creation integration endpointNever embed in Webflow

The existing AuthEnv also declares Cloudflare D1 and rate-limit bindings; these are Worker resources, not values to paste into a page.

3.3 Configuring Worker secrets

From the project directory (use the correct configuration for the target environment):

BASH · EXAMPLE
npx wrangler secret put INVITATION_API_KEY --config wrangler.dev.jsonc
npx wrangler secret put BETTER_AUTH_SECRET --config wrangler.dev.jsonc
npx wrangler secret put RESEND_API_KEY --config wrangler.dev.jsonc

Wrangler prompts for each value; it is not necessary to paste the secret into a command line or commit it to a file. Do not overwrite existing secrets casually: changing the Better Auth secret can disrupt sessions; changing an integration key requires updating callers. Check your Worker environment and backup/recovery plan before rotating.

3.4 Public JavaScript API origin

The observed SDK reads:

JS · EXAMPLE
const API = window.MEMBERS_API_URL ||
  ["https:", "", "members.methodfilm.com"].join("/");

For new sites, define the correct API URL before loading the SDK:

HTML · EXAMPLE
<script>
  window.MEMBERS_API_URL = "https://YOUR-MEMBERSHIP-API.example.com";
</script>

Note: The fallback in the observed SDK is Method Film-specific. The reusable template should remove that hardcoded fallback or replace it during installation. Only put public origin URLs here; never an API key.

4. Getting a new invitation API key

4.1 What the invitation API key is for

INVITATION_API_KEY is a private integration credential intended for trusted server-side systems that create invitations (for example, a checkout or automation backend). It is not used by ordinary website visitors or by the employee dashboard's cookie-authenticated admin routes.

4.2 Generate a high-entropy key

On your Mac:

BASH · EXAMPLE
openssl rand -hex 32

This yields 64 hex characters (256 random bits). Copy it into your password manager/secret store. Do not paste the value into shared documentation, Slack screenshots, Webflow, client JavaScript, Git, or an issue tracker.

4.3 Save it in Cloudflare

BASH · EXAMPLE
cd ~/webflow-memberships/membership-api
npx wrangler secret put INVITATION_API_KEY --config wrangler.dev.jsonc

When prompted, enter the generated secret. For different sites or environments, use different keys so an integration compromise does not affect other installations.

4.4 Use it in an integration

The project exposes a protected invitation-creation endpoint at /api/invitations. The observed design uses an Authorization: Bearer ... API key. The exact request body, responses, allowed origins, and server-side caller contract for that privileged route should be copied from its current src/index.ts implementation before publishing a production recipe.

Conceptual server-to-server example (VERIFY exact payload in repository):

BASH · EXAMPLE
curl -X POST "https://members.example.com/api/invitations" \
  -H "Authorization: Bearer $INVITATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"customer@example.com","courseKey":"example-course"}'

Set the shell environment variable in the trusted calling environment; never give the actual key to page visitors. courseKey follows the format [a-z0-9][a-z0-9_-]{0,99} in the observed invitation helper.

4.5 Rotate or revoke a key

  1. Identify every automation, checkout integration or backend that uses it.
  2. Generate a new 32-byte random value.
  3. Update the Worker secret and authorized callers in a coordinated change.
  4. Test an authorized invitation and an unauthorized invitation attempt.
  5. Monitor for errors after rotation.

The present system shows one INVITATION_API_KEY binding, not a documented multi-key management service. A replacement secret invalidates callers using the old value after the new secret takes effect. There is no confirmed dashboard button for creating individual invitation API keys.

5. Deploying a new installation

Status: Method Film and a separate test installation have been exercised, but the template has not yet been confirmed to contain every Milestone 14 update. Do not claim a one-command fresh install until the template and migration chain are reconciled.

5.1 Prerequisites

  • Cloudflare account with Workers, D1 and appropriate rate-limit bindings available.
  • Resend account and a verified sending domain.
  • Webflow site and access to Site Settings → Custom Code, page settings, element custom attributes and publishing.
  • Node.js/npm and Cloudflare Wrangler installed in a development environment.
  • Production hostname for the API (prefer a distinct subdomain).
  • A plan for backups and custom domain/DNS ownership.

5.2 Installation order

  1. Create or clone the membership API project from the approved template.
  2. Select Worker name, environment, API hostname and Cloudflare account.
  3. Create a new D1 database; bind it to DB in Wrangler.
  4. Configure both rate limit bindings expected by the Worker: SIGNUP_RATE_LIMITER and SIGNUP_IP_LIMITER.
  5. Run the complete schema/migration process, including user roles, suspension, entitlements, invitations and email changes.
  6. Set public configuration (SITE_URL, LOGIN_URL, ACTIVATION_URL, BETTER_AUTH_URL, ALLOWED_ORIGINS, etc.).
  7. Set secrets in the correct Worker environment.
  8. Configure/verify the Resend sending domain and EMAIL_FROM.
  9. Typecheck and deploy the Worker.
  10. Add API URL and browser SDK to Webflow global custom code.
  11. Add page elements and custom attributes per the page recipes below.
  12. Add the employee admin dashboard embed to a Webflow admin page.
  13. Promote the intended account to administrator using a reviewed, access-controlled operation.
  14. Test the user journey and administrator actions with non-production accounts.
  15. Record the new site's deployment identifiers and support contacts.

5.3 Useful commands for the current reference deployment

BASH · EXAMPLE
cd ~/webflow-memberships/membership-api
npx tsc --noEmit
npx wrangler deploy --config wrangler.dev.jsonc

Current reference D1 migrations were executed remotely with the following shape:

BASH · EXAMPLE
npx wrangler d1 execute membership-dev \
  --remote --config wrangler.dev.jsonc \
  --file=migrations/0006_member_email_changes.sql

Do not run an already-applied migration twice. Also do not point the example at a new site's database until you have updated its D1 database name and Worker config.

5.4 New-site worksheet

TEXT · EXAMPLE
Site name:
Public website URL:
Login URL:
Activation URL:
Forgot-password URL:
Reset-password URL:
Access-denied URL:
Worker name:
Worker custom domain:
Cloudflare account/environment:
D1 database name + ID:
Approved CORS origins (production/staging):
Resend verified domain:
EMAIL_FROM:
Invitation expiry days:
Course key list:
Administrator account:
Backup procedure:
Support contact:
SDK build/version:

6. Webflow site-wide custom code

6.1 Where to enter code in Webflow

  • Site Settings → Custom Code → Head Code: scripts/configuration or CSS required across all pages.
  • Site Settings → Custom Code → Footer Code: JavaScript that should load on all pages after page markup, such as the browser SDK.
  • Individual Page Settings → Custom Code → Inside @@PROTECT3@@: special behavior that applies only to that page, including whole-page access-gate CSS.
  • Individual Page Settings → Before @@PROTECT4@@: page-specific scripts that depend on its forms/markup, when required.
  • Designer → Element Settings → Custom Attributes: the data-* values described in the next section.

Publish the site after changing code or attributes. Some Webflow code does not behave identically in Designer preview and the published site.

6.2 Recommended global head code

This is a recommended install pattern, not a verbatim export of the currently published Method Film global code:

HTML · EXAMPLE
<script>
  window.MEMBERS_API_URL = "https://members.methodfilm.com";
</script>
<style>
  /* Hide conditional elements until the SDK establishes state. */
  [data-member-only],
  [data-guest-only] { visibility: hidden; }

  /* A course authorization marker is assigned by the SDK. */
  [data-course-only]:not([data-course-authorized="true"]) {
    display: none !important;
  }
</style>

This gives the SDK time to determine session state and prevents visually exposing member/guest sections before its initial check. The exact currently published site-wide CSS was not provided; compare it against this example before replacing anything.

6.3 Recommended global footer code

Load the published SDK file once. Confirm its actual deployed public path (/members.js, /sdk/members.js, etc.) from public/, the Worker assets configuration, and the browser Network panel before pasting a final script URL.

HTML · EXAMPLE
<!-- Example only: confirm the actual SDK asset path first. -->
<script src="https://members.methodfilm.com/REPLACE-WITH-DEPLOYED-SDK-PATH" defer></script>

Avoid embedding an entire second copy of members.js on individual pages. Duplication may attach form submit handlers twice, cause repeated calls, or produce conflicting visibility changes.

6.4 Why loading order matters

  1. Browser receives Webflow HTML and CSS.
  2. Global configuration sets window.MEMBERS_API_URL.
  3. Membership SDK runs.
  4. SDK calls the auth and access endpoints using credentials: "include".
  5. SDK updates attributes and visibility or redirects if the page requires access.

If the SDK runs before its config is set, it falls back to the currently hardcoded Method Film API origin. If JavaScript fails or the network is unavailable, a protected page must fail closed visually.

6.5 How to inspect what is actually installed

In a published site browser console:

JS · EXAMPLE
console.log(window.MEMBERS_API_URL);
console.log(document.documentElement.hasAttribute('data-members-ready'));
console.log(document.documentElement.getAttribute('data-page-access-granted'));

Inspect the Network panel for members.js, /api/auth/get-session, and /api/enrollment/access. Do not treat missing requests as proof that everything is working.

7. Webflow element attributes — complete known reference

The following attributes are present in the inspected src/sdk/members.js. These are the known attribute contracts, not a speculative list of other attributes.

7.1 Visibility and identity attributes

AttributeApply toMeaning / usage
data-member-onlyAny element/sectionShown only when the SDK recognizes an authenticated member; otherwise hidden
data-guest-onlyAny element/sectionShown only for users who are not signed in
data-member-nameText elementSDK inserts the signed-in user's name using text content
data-member-emailText elementSDK inserts the signed-in user's email using text content
data-logoutButton or linkIntercepts clicks and signs the member out
data-members-readySet by SDK on document rootMarker indicating the SDK finished a state update; not an editor-entered flag

Webflow example: Select an account navigation link, open Custom Attributes, set Name data-member-only and leave Value blank. Select the sign-out link and add Name data-logout with blank Value. For name/email display, add data-member-name or data-member-email to a text element.

Common mistake: Do not manually mark an element as authenticated by setting SDK-owned attributes. The SDK decides visibility using API results.

7.2 Course-specific attributes

AttributeApply toMeaning / usage
data-course-only="course-key"Course-specific elementSDK checks the member's course key list and adds/removes authorization marker
data-course-authorized="true"SDK-managed output on course-specific elementRepresents an authorized course element; do not set manually
data-page-course-only="course-key"One element on protected page, e.g. page wrapperInstructs SDK to gate the whole page by membership in that course
data-page-member-only="true"One element on protected pageRequires any authenticated member to view the page
data-page-access-granted="true"SDK-managed output on document rootIndicates the page guard permitted access

Course key examples: admin-test-course, filmmaking-foundations, editing-basics. These are illustrations only; a key must exactly match the value held in course_entitlements and on the invitation.

Gate a whole course page

  1. Add a wrapper element to the Webflow page.
  2. Set its custom attribute to Name data-page-course-only; Value filmmaking-foundations.
  3. Include fail-closed page-level CSS in the page head (next section).
  4. Publish; test as a guest, a member without this course, and an entitled member.

The SDK redirects guests to /login (with a safe returnTo parameter) and signed-in members lacking the required course to /access-denied.

Show one section only to entitled members

Set Name data-course-only; Value filmmaking-foundations on the section. The SDK sets data-course-authorized="true" if the user owns that course. The global CSS shown earlier hides unmarked sections.

Caution: An unentitled user can still inspect delivered Webflow HTML in browser developer tools. For sensitive video URLs or documents, use authenticated delivery rather than only this attribute.

7.3 Authentication form attributes

AttributeApply toMeaning / usage
data-auth-form="login"<form>Bind the SDK email/password login handler
data-auth-form="forgot-password"<form>Bind password-reset email request flow
data-auth-form="reset-password"<form>Bind new-password submission flow
data-auth-field="email"Email inputIdentify the email field
data-auth-field="password"Password inputIdentify the login password field
data-auth-field="new-password"Password inputIdentify new password field
data-auth-field="confirm-password"Password inputIdentify password confirmation field
data-auth-messageText/div near formSDK writes success/errors here; it can create one if absent
data-auth-redirect="/some-page"Login formFallback post-login destination if no safe returnTo parameter exists

Example login form markup (Webflow Designer should generate the actual form):

HTML · EXAMPLE
<form data-auth-form="login" data-auth-redirect="/account">
  <input type="email" data-auth-field="email" required>
  <input type="password" data-auth-field="password" required>
  <button type="submit">Sign in</button>
</form>
<div data-auth-message role="status"></div>

Login return precedence: SDK first considers the URL's ?returnTo= value if it is a safe same-origin path; if none is permitted, it falls back to data-auth-redirect. It rejects unsafe external destinations and certain redirect loops.

Webflow form warning: The SDK prevents Webflow's native submission. Ensure your selectors sit on the actual <form> and <input> elements, not on a visual parent/wrapper. If a form reloads the page or produces a native Webflow success message, the SDK may not have attached.

7.4 Enrollment attributes

AttributeApply toMeaning / usage
data-enrollment-action="redeem"Button/link on activation pageTrigger redemption of the invitation for an authenticated account
data-enrollment-messageText/div on activation pageSDK shows enrollment or activation feedback

The SDK also contains special processing for a page whose path is exactly /activate (ignoring a trailing slash). Keep that in mind when changing the activation URL; the backend setting alone does not change the SDK path detection.

7.5 Other selectors found in admin HTML

The admin dashboard primarily uses its own id="ma-..." elements, not the public SDK's data-* interface. Treat the admin Code Embed as a complete managed component. Avoid copying individual internal IDs elsewhere on the same page.

8. Page-by-page Webflow setup

8.1 Public page with guest/member navigation

Global head: set API origin; install initial visibility CSS. Global footer: load the SDK. Attributes: add data-guest-only to Sign In / Register controls and data-member-only to Account / Sign Out controls; add data-logout to Sign Out. Expected behavior: SDK switches the visible controls when it loads the session.

8.2 Login page (/login in current SDK)

Designer: native Webflow form containing email, password and submit control. Form attribute: data-auth-form=login. Inputs: data-auth-field=email, data-auth-field=password. Status element: data-auth-message. Optional form redirect: data-auth-redirect=/account (replace destination for your site).

How it works: POST /api/auth/sign-in/email with cookies included; refresh session; redirect to approved returnTo or fallback attribute. Public sign-up is disabled.

Test: wrong password, disabled member, right password, direct ?returnTo=/course/path, external returnTo attempt, repeat sign-in while already logged in.

8.3 Member-only page

  • Add data-page-member-only="true" to a page element (prefer top-level page wrapper).
  • Add an early fail-closed style in the page head, for example:
HTML · EXAMPLE
<style>
  html:not([data-page-access-granted="true"]) body {
    visibility: hidden;
  }
</style>

This is a recommended defensive CSS pattern, not a verified exact copy of Method Film's existing page head code. It intentionally hides the page before the guard finishes; make sure redirects work and create a visible fallback if the SDK cannot load. The SDK redirects guests to login with returnTo.

8.4 Course-restricted page

  • Set data-page-course-only="YOUR-COURSE-KEY" on a page wrapper.
  • Include fail-closed page-head CSS as above.
  • Provide a published /access-denied page in the reference installation.
  • Grant the course key to a test account before testing.
  • Check guest, signed-in unentitled, and entitled behavior.

Do not confuse data-course-only (one element) and data-page-course-only (whole-page guard).

8.5 Forgot password page

  • Create a form with data-auth-form="forgot-password".
  • Add email input with data-auth-field="email".
  • Add data-auth-message nearby.
  • SDK calls /api/auth/request-password-reset and uses the site recovery destination /reset-password.

The observed SDK uses a literal /reset-password URL. Update the SDK if a new site uses a different route.

8.6 Reset password page (/reset-password in current SDK)

  • Create form with data-auth-form="reset-password".
  • Add data-auth-field="new-password" and data-auth-field="confirm-password".
  • Add data-auth-message.
  • The SDK reads the token/error query parameters, removes them from the visible URL using browser history, then submits the reset request.
  • Test valid token, invalid/expired token, short password, mismatching passwords, and successful sign-in afterward.

8.7 Invitation activation page (/activate in current SDK)

  • The invitation email links to ACTIVATION_URL?token=....
  • The browser SDK recognizes /activate, reads the token and displays activation state.
  • Include data-enrollment-message for progress/errors.
  • For an existing-account enrollment path, a control with data-enrollment-action="redeem" invokes redemption.
  • New-account setup is backed by /api/enrollment/complete-setup, which requires valid invitation details, a name and password.

VERIFY IN REPOSITORY: The precise activation form HTML and its setup input selectors were not included in the provided SDK excerpts. Copy the currently published /activate page structure rather than inventing field names or a new signup flow.

8.8 Access-denied page

  • Publish /access-denied for the SDK's current redirect.
  • Explain that the user is signed in but does not own the required course.
  • Provide navigation to account, courses, or support; do not imply that reloading can grant access.

8.9 Admin page (/admin)

  • Create a Webflow page and optionally apply Webflow's page password.
  • Insert the complete latest admin dashboard HTML/CSS/JS into one Code Embed.
  • The dashboard verifies /api/admin/me before exposing its management UI.
  • API routes independently enforce role='admin'; the Webflow page password is an additional barrier, not authorization for the API.
  • Publish and sign in using an authorized administrator account.

Current reusability limitation: The inspected dashboard HTML contains const API = 'https://members.methodfilm.com';. Before installing on a different site, change it to read the same configured public origin, for example window.MEMBERS_API_URL, or inject a site-specific value during packaging. Do not assume a fully dynamic dashboard without this change.

9. Authentication and session behavior

9.1 Invitation-only registration

Regular POST /api/auth/sign-up/email does not permit arbitrary public signups. The invitation setup handler validates the invitation token before invoking Better Auth registration. Activation verifies email ownership through the invited address.

9.2 Sessions

Better Auth manages sessions in D1. Browser fetch calls use credentials: "include" so cookies accompany requests to the membership API. The Worker uses secure cookies and an approved-origin CORS policy.

9.3 Account suspension

  • A user.disabled_at timestamp indicates suspension; NULL means active.
  • A session creation hook rejects suspended users.
  • Disable action removes current sessions.
  • The enrollment access and redemption endpoints also check suspended status.
  • The account must be re-enabled before normal sign-in succeeds.

Remaining regression test: Confirm complete denial from a disabled test account's actual browser session; admin API success alone does not prove every frontend flow.

9.4 Email change

  • Admin initiates the change to a different recipient.
  • Verification mail is delivered to the new address.
  • A one-time, hashed token is stored in member_email_changes with a one-hour expiry.
  • After verification, the user email changes and existing sessions are revoked.
  • The confirmation page uses LOGIN_URL to send the person back to sign in.
  • Admins cannot use the ordinary member email-change action to change their own administrator email.

9.5 Member deletion

Deletion is a destructive employee action, requiring explicit confirmation. The backend has protections against administrator self-deletion and handles related course/auth records. The relevant schema has NO ACTION foreign keys that must be cleaned up transactionally. Still requires a final test on a disposable member.

10. Invitation and activation lifecycle

10.1 What is stored

An invitation records id, email, course_key, a hash of its activation token, optional checkout-session ID, expiry, creation date, and redemption information. The raw invitation token is returned only during creation/replacement to build the email URL.

10.2 Status

StatusMeaningEmployee options
PendingToken exists, has not expired or been redeemedResend/replace, correct email (if allowed), remove (if allowed)
ExpiredCurrent time exceeds expiry; not redeemedIssue a replacement link
RedeemedUsed to activate/enroll an accountRead-only; course access managed on Members tab

10.3 Create invitation

Employee selects Invite Member in dashboard, enters recipient email, optional name, and a valid course key. The backend calls createInvitation, stores the hashed token, and sendInvitation emails an activation URL. The Worker validates INVITATION_EXPIRY_DAYS between 1 and 30; Method Film uses 7 days.

10.4 Resend / replace invitation

The deployed endpoint is POST /api/admin/invitations/:id/replace and expects the recipient email in its body. It generates a new random token, overwrites the stored hash, resets expiration, and sends a new invitation email. The earlier token no longer matches the database. This invalidation was exercised and confirmed in the browser.

If the email send fails after the token is replaced, the old link remains invalid and the administrator should use Resend again. Employees should not assume the original token is still usable.

10.5 Correct a mistaken email

An invitation can be replaced with a corrected email if it has not been redeemed and is not restricted by its checkout association. The replacement also rotates the token. For checkout-linked invitations, changing recipient is blocked, because those are connected to an order.

10.6 Remove invitation

The deployed endpoint is POST /api/admin/invitations/:id/remove. It deletes an unredeemed manual invitation without a checkout association. The link immediately becomes unusable. It does not delete a member account or revoke a course entitlement that was previously granted.

10.7 Restrictions

  • Redeemed invitations may not be replaced or removed.
  • Checkout-linked invitations cannot be removed using the ordinary admin action.
  • Checkout-linked recipient email may not be changed through ordinary replacement.
  • Administrator authentication is required for management actions.
  • Removal is distinct from revoking an enrolled member's course entitlement.

10.8 Recommended staff instructions

Customer lost email: Invitations → search email → Manage → Resend New Link. Email address typo: Invitations → Manage → Correct Email, then send. Invitation issued by mistake: Invitations → Manage → Remove (if eligible). Already redeemed / wrong course: Find member under Members and review courses. Do not attempt to erase redemption history.

11. Courses, entitlements and access control

11.1 Course keys

Course ownership uses string keys. The same key must be used when inviting, granting, checking the API and setting attributes. The observed validator permits lowercase letters, digits, underscore and hyphen, starting with a letter or digit, up to 100 characters.

Examples: filmmaking-foundations, editing_101, course2.

11.2 Entitlement records

course_entitlements associates user_id with course_key, tracks its source invitation when available, the grant time, and nullable revoked_at. The entitlement lookup only returns rows with revoked_at IS NULL.

11.3 Grant/revoke versus UI visibility

  • Grant: gives a user the relevant backend course entitlement.
  • Revoke: marks that entitlement revoked in the database.
  • SDK course marker: reflects backend data in the current page's DOM.
  • Page guard: routes a user away if the requisite course is absent.

A user already sitting on a page may not immediately be navigated away when another administrator revokes access. Sensitive resources must enforce authorization on access, not just on initial page load.

11.4 Adding a course to Webflow

  1. Select a canonical course key.
  2. Ensure invitations and employee grants use precisely that key.
  3. Build the course landing/content page.
  4. Apply data-page-course-only="KEY" to its main wrapper for a whole-page gate.
  5. Add page-head hiding CSS and a known access-denied route.
  6. Mark any course-specific buttons/cards with data-course-only="KEY".
  7. Test with entitled and unentitled accounts.
  8. Record the key in your internal course catalog/documentation.

12. Employee administrator dashboard

12.1 Basic access

The Webflow dashboard presents its controls only after a successful /api/admin/me request. The backend checks authenticated session and role; unauthenticated calls to management routes return 401. Administrative users are distinct from ordinary members.

12.2 Members tab

  • Member list with server-side search and page size of 25 (backend enforces a maximum of 50).
  • Member detail modal shows name, email, role, verification status, active/disabled state, joined date and courses.
  • Edit name.
  • Request password reset email.
  • Grant/revoke courses.
  • Disable/enable accounts.
  • Start verified email change.
  • Permanently delete with a confirmation step.

12.3 Invitations tab

  • Search by email, filter by pending/redeemed/expired/all, and paginate.
  • Create and send invitations.
  • Manage an unused invitation: resend fresh link, correct email and resend, or remove if eligible.
  • Redeemed invitations are read-only.

12.4 Operational procedures

Reset password: Members → Manage → Send Password Reset. Member receives an email and completes the reset themselves. Suspend account: Members → Manage → Disable Account; confirm. Sessions are deleted. Restore account: Members → Manage → Enable Account. Member signs in again. Change email: Members → Manage → Edit Email. The new recipient verifies before the change takes effect. Delete account: Members → Manage → Permanently Delete. Confirm identity and consequences; this is irreversible. Add/revoke course: Members → Manage → Owned courses → Add Course / Remove.

12.5 Admin embed maintenance

Use one complete dashboard Code Embed instead of combining versions of the old and new dashboard. Check browser console for duplicate element IDs or script errors if buttons stop responding. Make changes in a versioned source HTML file, publish Webflow, and perform a quick end-to-end smoke test.

13. API reference

Base URL (Method Film): https://members.methodfilm.com Browser-authenticated calls: send credentials: "include". Privileged integration calls: use server-side Bearer credential where required. Responses: JSON unless otherwise specified. Authorization: Most /api/admin/* routes require a valid admin session.

13.1 Core and authentication

MethodRoutePurpose
GET/Health/service summary
GET/api/auth/get-sessionCurrent session
POST/api/auth/sign-in/emailSign in with email/password
POST/api/auth/sign-outSign out
POST/api/auth/request-password-resetBegin password reset
POST/api/auth/reset-passwordComplete password reset
POST/api/auth/sign-up/emailNormally disabled for public self-registration

Other Better Auth routes exist through the wildcard auth handler. Refer to installed Better Auth routes for their precise contract; do not assume every upstream endpoint is intentionally exposed as part of this application's public support API.

13.2 Enrollment

MethodRoutePurpose
GET/api/enrollment/accessAuthenticated member's active course keys
POST/api/enrollment/inspectInspect invitation token/eligibility
POST/api/enrollment/complete-setupCreate account via validated invitation
POST/api/enrollment/redeemRedeem course invitation for an existing member
POST/api/invitationsTrusted integration invitation creation; Bearer secret

VERIFY IN REPOSITORY: Complete request/response schemas for inspect, complete-setup, redeem, and privileged /api/invitations before publishing code examples externally.

13.3 Admin member routes

MethodRoutePurpose
GET/api/admin/meConfirm admin identity
GET/api/admin/members?page=1&limit=25&search=Search/paginate members
GET/api/admin/members/:idMember details + active courses
POST/api/admin/members/:id/detailsUpdate name
POST/api/admin/members/:id/courses/grantGrant a course
POST/api/admin/members/:id/courses/revokeRevoke a course
POST/api/admin/members/:id/password-resetSend password reset
POST/api/admin/members/:id/disableSuspend member and revoke sessions
POST/api/admin/members/:id/enableRestore member account
POST/api/admin/members/:id/email-changeRequest verified email change
GET/api/admin/email-change/verify?token=...Verify new address; returns success/failure HTML
POST/api/admin/members/:id/deletePermanently delete member

Known examples:

JS · EXAMPLE
// From a signed-in admin browser, on an approved origin.
const api = window.MEMBERS_API_URL;
const response = await fetch(`${api}/api/admin/members?page=1&limit=25`, {
  credentials: 'include'
});
const directory = await response.json();
console.log(directory.members, directory.total);
JSON · EXAMPLE
// Example body for course grant/revoke — JSON comments are for explanation only.
{"courseKey":"filmmaking-foundations"}
JSON · EXAMPLE
{"name":"Updated Member Name"}
JSON · EXAMPLE
{"email":"corrected@example.com"}

VERIFY IN REPOSITORY: Exact deletion confirmation payload, any additional validation rules and response field names before using API directly from another client.

13.4 Admin invitation routes

MethodRoutePurpose
GET/api/admin/invitations?page=1&limit=25&search=&status=pendingList/filter/paginate invitations
POST/api/admin/invitationsCreate invitation and email recipient
POST/api/admin/invitations/:id/replaceRotate token, optionally change email, reset expiry, send new email
POST/api/admin/invitations/:id/removeRemove eligible unused manual invitation

Create invitation body:

JSON · EXAMPLE
{
  "email": "customer@example.com",
  "name": "Customer Name",
  "courseKey": "filmmaking-foundations"
}

Replace body:

JSON · EXAMPLE
{"email":"corrected@example.com"}

Remove uses the invitation ID in the URL and no required request body in the observed implementation.

13.5 Error conventions and testing

HTTP statusTypical meaning
200/201Successful query or operation
400Invalid input
401Missing/invalid authentication
403Authenticated but not authorized or account blocked
404Record not found
409Action conflicts with current state (redeemed, disabled, protected)
410Link expired/used/invalid
429Rate limit reached
500Server/database error
502Email delivery failure or upstream mail issue

These are typical statuses observed in the code; each endpoint may use a subset. Do not build UX solely on status text—read the returned JSON error where available.

13.6 Test a protected route without a session

BASH · EXAMPLE
curl -i -X POST \
  https://members.methodfilm.com/api/admin/invitations/test-id/remove

Expected unauthenticated result: 401. Never test destructive routes using a valid admin cookie against a real customer ID without explicit intent.

14. Transactional email

14.1 Sender identity

Current reference sender: Method Film <accounts@send.methodfilm.com> using Resend. An installation on another site must verify its own sender domain and update EMAIL_FROM and SITE_NAME.

14.2 Mail types

  • Course invitation and replacement invitation.
  • Better Auth email verification.
  • Password reset.
  • Member email-change verification sent to the new address.
  • Best-effort informational email-change notice to the old address.

14.3 Invitation email contents

sendInvitation produces a subject using SITE_NAME, optional greeting using the customer's name, an activation URL containing ?token=, and configured expiry language. It escapes HTML in the name and URL before insertion into mail markup.

14.4 Delivery failure behavior

  • Creating an invitation may save the invitation before mail delivery; an unsuccessful send can leave a valid invitation record that needs resending.
  • Replacing an invitation revokes the previous token before attempting mail; if mail delivery fails, repeat replacement/resend.
  • Inspect Worker logs and Resend delivery records before assuming the person received a message.
  • Do not log raw tokens or password values.

14.5 Branded verification result page

The email-change verification route uses a branded HTML response and a LOGIN_URL redirect after success. Invalid/used/expired links produce a failure page. Once the link is consumed, clicking it again should not produce another successful account update.

15. Database schema and migrations

15.1 Important tables

TableMain use
userIdentity, name, email, role, verification state, disabled timestamp
sessionActive authentication sessions
accountLinked credential/provider account records
verificationAuthentication/verification support
enrollment_invitationsPending/redeemed course invitations and hashed tokens
course_entitlementsCourse ownership and revocation status
member_email_changesPending email-change tokens and expiry

15.2 Migrations confirmed during development

  • 0002: invitation records.
  • 0003: course entitlements.
  • 0004: member/admin role column.
  • 0005: user.disabled_at nullable timestamp.
  • 0006: pending email-change table/indexes.

Earlier Better Auth/Drizzle migrations create the foundational identity tables. Confirm migration ordering, the initial migration and SQL content with repository files before automated fresh installs.

15.3 Foreign keys and deletion

During development, course_entitlements.user_id and enrollment_invitations.redeemed_by_user_id were confirmed to have NO ACTION foreign-key behavior. Session and linked account records use cascading user deletion. Consequently, deletion requires a deliberate database sequence/transaction, not a simple raw DELETE FROM user.

15.4 Inspect remote database carefully

BASH · EXAMPLE
npx wrangler d1 execute membership-dev \
  --remote --config wrangler.dev.jsonc \
  --command="SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"

This is read-only. Avoid copying customer email addresses into documentation or support tickets unless necessary.

16. Security boundaries and known limitations

  1. Page passwords are not API authorization. The API must check session + admin role.
  2. Client-side hiding is not data protection. Webflow may deliver HTML and asset URLs to anyone who requests the page.
  3. Never expose Worker secrets in browser code. INVITATION_API_KEY, BETTER_AUTH_SECRET, and RESEND_API_KEY are server-only.
  4. Invitation tokens are single-use credentials. Avoid displaying raw tokens in logs or analytics URLs. Hash them in storage.
  5. Respect approved origins. Browser requests are configured with cookies and restricted by allowed origins; configure both production and staging deliberately.
  6. Separate environments. Do not point new development sites at Method Film's production database by accident.
  7. Auth and member status: Backend entitlement checks should reject disabled accounts even when a stale browser state appears active.
  8. Rate limits: Signup/invitation paths use Cloudflare rate-limit bindings; verify actual thresholds and production behavior.
  9. Admin actions: Deletion, email changes, and course grants need operational review; role-based audit trails remain a future enhancement.
  10. Remaining tests: Disposable-member deletion, actual blocked login, redeemed invitation rejection, and complete production mobile/regression checks were not all confirmed at the time of writing.

17. Troubleshooting and diagnostics

Member/guest elements never appear

  • Confirm the SDK script loaded (Network panel, HTTP 200, valid MIME type).
  • Confirm window.MEMBERS_API_URL is set before the SDK.
  • Inspect /api/auth/get-session and /api/enrollment/access responses.
  • Check browser console errors and expected data-* attributes.
  • Confirm the domain is listed in ALLOWED_ORIGINS.

Login form behaves like a native Webflow form

  • Put data-auth-form="login" on the real <form>.
  • Put data-auth-field on real <input> elements.
  • Ensure the SDK isn't missing/loaded twice.
  • Add a data-auth-message element for useful errors.

Login succeeds but page access is denied

  • Compare the exact data-page-course-only key with stored course entitlement keys.
  • Confirm the entitlement isn't revoked.
  • Confirm the member isn't disabled.
  • Inspect /api/enrollment/access using the same signed-in browser.

Guest gets an unexpected redirect

  • Check returnTo on login and whether the SDK's safe-path function accepts it.
  • Review literal path assumptions in SDK (/login, /access-denied).
  • Confirm page head guard styling doesn't inadvertently hide the login page itself.

Invitation link says invalid, used or expired

  • Check whether the invitation was redeemed, replaced, removed or expired.
  • If replaced, old links must fail; use the newest delivered email.
  • If delivery failed during resend, resend again.
  • Do not manually change hashes in D1 to rescue an invitation.

Invitation recipient entered incorrectly

  • Invitations → Manage → Correct Email.
  • New token replaces the previous one; original email recipient's link is invalid.
  • Paid/checkout-linked invitations cannot be redirected using ordinary email correction.

Admin dashboard says administrator required

  • Confirm the browser is signed into the correct site/account.
  • Check GET /api/admin/me in the console.
  • Confirm the account role is admin in D1.
  • Ensure the API origin and browser origin are correctly configured.

Worker deploy reports CommaExpected

  • Check commas around entries in wrangler.dev.jsonc.
  • Use nl -ba wrangler.dev.jsonc | sed -n '20,45p' to view line numbers.
  • Run npx tsc --noEmit before re-deploying; TypeScript success doesn't validate Wrangler JSONC syntax.

Email isn't arriving

  • Verify Resend API key and sending-domain verification.
  • Check sender EMAIL_FROM, recipient spelling, spam filters and Resend event logs.
  • Inspect Worker logs for mail delivery errors.
  • For invitations, confirm whether the DB token was already rotated even when sending failed.

SDK seems hardcoded to Method Film on a new install

  • Confirm the browser API origin config.
  • Replace/refactor SDK's hardcoded /login, /activate, /access-denied, /reset-password assumptions.
  • Replace/refactor admin dashboard's literal Method Film API origin.
  • Ensure LOGIN_URL, ACTIVATION_URL, SITE_URL and approved origins point at the new site.

18. Maintenance, backups and updates

18.1 Deployment workflow

  1. Record current Worker version and database state.
  2. Back up production database (use Cloudflare-supported backup/export process, verify restore procedure).
  3. Apply source changes in version control.
  4. Run npx tsc --noEmit and any available automated tests.
  5. Review and apply new migrations to the correct environment.
  6. Deploy the Worker.
  7. Publish any corresponding Webflow embed/global/page-code changes.
  8. Smoke-test login, invitation creation, course access, and admin authorization.
  9. Document release date, change summary and rollback steps.

18.2 Database operations

Use Wrangler --remote only when you intentionally target the live D1 database. Check the bound database name and Cloudflare account before executing mutations. Record migration number and application date.

18.3 Versioning the Webflow code

Store copies of:

  • Entire global head code.
  • Entire global footer code.
  • Per-page head/body code for login, activate, reset-password, access-denied, courses and admin.
  • Final admin Code Embed.
  • Source members.js and its deployed asset URL/version.

The Webflow Designer isn't a substitute for version-controlled source. Use release notes so future changes to SDK selectors can be coordinated with the site attributes.

18.4 Operational checks

  • Login and logout.
  • Password recovery.
  • New invitation and delivery.
  • Expired/replaced invitation behavior.
  • Course entitlement gating.
  • Admin directory and invitation management.
  • Suspended-account behavior.
  • Resend sender domain and email events.
  • D1 backups and known restore path.

19. New-site launch checklist

Cloudflare & database

  • ☐ Dedicated Worker and D1 database created.
  • ☐ Correct bindings and rate limits.
  • ☐ Schema/migrations applied, verified with table listing.
  • ☐ Custom API domain and TLS ready.
  • ☐ Site-specific config set.
  • ☐ Private secrets created securely (unique per site).
  • ☐ Resend domain and sender verified.
  • ☐ Admin account promoted only after identity confirmed.

Webflow site-wide installation

  • ☐ Configure public API origin before SDK.
  • ☐ Add SDK once in footer.
  • ☐ Initial member/guest/course visibility CSS present.
  • ☐ Every page publishes without console errors.
  • ☐ Site's domain and any staged origins allowed by API.

Page-specific installation

  • ☐ Login form and correct input attributes.
  • ☐ Password recovery and reset forms.
  • ☐ Activation page and known invitation selectors.
  • ☐ Access-denied page.
  • ☐ Member-only page guards where needed.
  • ☐ Course-only page guards using correct keys.
  • ☐ Fail-closed page head styles.
  • ☐ Admin page with complete current embed.
  • ☐ Mobile layouts checked.

Functionality and security acceptance

  • ☐ Visitor sees guest content.
  • ☐ Member logs in, sees name/email and signs out.
  • ☐ Unauthorized visitor can't access protected course resources.
  • ☐ Valid invitation activates an account.
  • ☐ Old invitation invalid after resend/removal.
  • ☐ Redeemed invitation cannot be changed.
  • ☐ Disabled member cannot create/use session or access course.
  • ☐ Admin can grant and revoke course access.
  • ☐ Email change verifies new address and redirects to configured login.
  • ☐ Permanent deletion works on disposable member.
  • ☐ Non-admin gets 401/403 for admin routes.
  • ☐ Emails delivered from the correct sender.
  • ☐ Backup and recovery process tested.

20. Outstanding documentation and development work

These items are not yet proven complete by the source excerpts or test results available:

  1. Export and preserve the exact live Webflow Site Settings head/footer code. The examples in this manual are recommended patterns and should be reconciled with the actual published site.
  2. Export the exact head and before-body code for each relevant Webflow page. Capture current login, activation, course gate, password recovery, access-denied, and admin page code.
  3. Audit all frontend SDK paths for reusability. LOGIN_URL on the Worker is configurable, but the observed SDK still uses hardcoded paths.
  4. Make the admin dashboard API URL configurable. The inspected dashboard still has a literal Method Film API origin.
  5. Verify the deployed SDK asset URL and setup snippet from Wrangler assets/public and site source.
  6. Complete exact activation form reference from the published /activate page and the full SDK source.
  7. Document complete privileged integration endpoint schema (POST /api/invitations) from current source, including its precise authorization and JSON contract.
  8. Synchronize reusable template with LOGIN_URL, new admin endpoints, database migrations, admin HTML and invitation management.
  9. Test a clean new-site install from the updated template.
  10. Finish last Milestone 14 checks: disabled-account browser access, disposable-member deletion, redeemed-invite admin restrictions, and unauthorized access to the new admin endpoints.
  11. Perform a mobile/keyboard/accessibility review of the admin dashboard and activation flow.
  12. Document and rehearse D1 backup/restore, plus failed-email recovery.
  13. Consider audit logs for destructive or sensitive employee operations in a future milestone.

This is deliberately a working document. Turn the verified sections into published documentation now; use the outstanding list to replace examples and assumptions with your exact production snippets.

21. Source-of-truth appendix

Source code used as reference for this manual

  • src/sdk/members.js: provided terminal output describing browser selectors, forms, session handling and page guards.
  • src/enrollment/invitations.ts: provided source of token issuance, token hashing, expiration and invitation email composition.
  • src/auth.ts: provided Better Auth configuration and AuthEnv shape.
  • src/index.ts: provided snippets for enrollment access, account setup, email change, admin member actions, invitation management and account suspension.
  • Current admin HTML: combined Webflow dashboard used for member and invitation tables and modals.
  • Actual terminal results: successful Cloudflare deployments, D1 migration execution, TypeScript validation and API authorization checks.
  • User test results: email change verification succeeded; invitation create, resend, correction and removal worked; previous activation links were rejected after invalidation.

Source freshness rule

Whenever an API endpoint, SDK selector, site-wide Webflow snippet or Worker environment variable changes, update this manual in the same deployment. The code and deployed configuration always take precedence over this document if a discrepancy is found.

Suggested website documentation navigation

For a password-protected documentation hub, split this Markdown into the following pages or anchors:

  1. Getting Started: Overview, architecture, new installation.
  2. Site Configuration: URLs, Cloudflare bindings, keys, domains and secrets.
  3. Webflow Setup: Global head/footer, page head, page-specific forms.
  4. Attributes Reference: All data-* attributes with copyable examples.
  5. Membership & Courses: Login, invitation activation, gates, entitlements.
  6. Employee Dashboard: Member and invitation workflows.
  7. API Reference: Routes, authorization, payloads and error codes.
  8. Operations: Troubleshooting, backups, security and release procedures.

End of Method Film Membership Platform Documentation, version 1.0 working draft.