Title: GSheet Membership
Author: gsheetplugins
Published: <strong>август 20, 2026</strong>
Last modified: септември 25, 2026

---

Search plugins

![](https://ps.w.org/membership-google-sheets/assets/banner-772x250.png?rev=3658609)

![](https://ps.w.org/membership-google-sheets/assets/icon-256x256.png?rev=3658640)

# GSheet Membership

 By [gsheetplugins](https://profiles.wordpress.org/gsheetplugins/)

[Download](https://downloads.wordpress.org/plugin/membership-google-sheets.4.9.0.zip)

 * [Details](https://mk.wordpress.org/plugins/membership-google-sheets/#description)
 * [Reviews](https://mk.wordpress.org/plugins/membership-google-sheets/#reviews)
 *  [Installation](https://mk.wordpress.org/plugins/membership-google-sheets/#installation)
 * [Development](https://mk.wordpress.org/plugins/membership-google-sheets/#developers)

 [Support](https://wordpress.org/support/plugin/membership-google-sheets/)

## Description

#### Access — the login and page gate

**GSheet Membership** lets you use a **private** Google Sheet as your membership
database.

 * **Truly private** — the sheet is never shared publicly. Access is granted exclusively
   to a Google Service Account that you control.
 * **Login gate** — only users whose email address appears in the sheet can log 
   in to WordPress.
 * **Membership codes** — each row contains an email address and a membership code(
   e.g. GOLD, SILVER, PLATINUM).
 * **Per-page restrictions** — set required membership code(s) on any page or post
   via a sidebar panel.
 * **Flexible access-denied handling** — redirect to a custom URL or show a configurable
   inline message.
 * **Encrypted roster cache** — the entire fetched roster is encrypted as one authenticated
   payload before it is cached in a non-autoloaded WordPress option. On a cache 
   hit, the whole payload is decrypted for the existing lookups; it is not encrypted
   or decrypted one member at a time. The OAuth2 access token is cached separately.
 * **Site Mode** — every site runs as Main (a single roster) or Chapter (its own
   local roster, alongside or instead of the main one) — Chapter mode is enabled
   by the separate Chapters add-on.
 * **Expiration handling** — warn members as their membership approaches its end,
   grant a configurable grace period after it lapses, and show a custom message 
   once access is revoked.
 * **Extra user-info fields** — map any additional sheet columns (address, phone,
   region, or anything else you track) so members can view and verify them on the
   front end via `[gsma_user_info]`.
 * **Custom login page** — host the sign-in form on any WordPress page you choose
   with the `[gsma_login_form]` shortcode, instead of WordPress’s default login 
   screen.
 * **Front-end appearance controls** — force the login form and status panel to 
   keep their original colors if your theme recolors them, and scale their text 
   size independently of your theme.
 * **Two code-matching modes** — require an exact membership code, or “letters mode,”
   where a member’s code must simply contain every letter in the required set (e.
   g. a required code of “AB” matches any member code containing both A and B).

#### How it works

The plugin authenticates using a **Google Service Account** — a special non-human
Google account your server uses to read and update the sheet. You share the spreadsheet
with it; no one else can access the sheet. With OpenSSL available, the saved service-
account JSON is encrypted at rest; the roster cache is only written when it can 
be encrypted. The two use separate keys derived from this WordPress installation’s
security salts. They are not exposed in the browser or source code. The entire cached
roster is decrypted in server memory when it is read; this does not encrypt old 
backups made before the upgrade.

#### Signing in

There’s no separate account-creation step for members. A member already on the roster
enters their email on your sign-in page: the first time, a 6-digit verification 
code is emailed to them and they’re prompted to create a password after entering
it; after that, they sign in with the password directly. See the FAQ entry below
for the full flow.

#### Optional Pro add-on

**GSheet Membership Pro** adds authenticated mailbox sending, Group Email (including
Mailchimp campaign delivery), Email-in, File Delivery links and optional PDF watermarks,
PayPal checkout, gift purchases, editable membership levels, and admin tools to 
retry failed sheet writes and refund supported purchases. Pro can also sync roster
contacts to Mailchimp and EmailOctopus without sending a campaign; EmailOctopus 
is a sync option, not a campaign-delivery gateway. The Access and Sales admin screens
show read-only previews of relevant Pro features and a direct upgrade button. After
purchasing, install and activate the Pro add-on, then complete its license activation;
the free plugin’s existing settings stay in place.

### Built for organizations with branches or chapters

Most membership plugins assume one site and one member list. Many real organizations
don’t work that way: a national society with local chapters, an alumni association
with regional groups, a union with locals. GSheet Membership was designed for exactly
that structure:

 * **One national roster, many chapter rosters** — the national organization keeps
   the master membership sheet; each chapter keeps its own local roster sheet. Both
   live in ordinary Google Sheets the organization owns.
 * **Each chapter runs its own WordPress site** — with its own pages, branding and
   admins, while membership checks stay consistent: a chapter site can honour the
   national roster, its local roster, or both.
 * **Chapter-local sales and email** — in chapter mode, membership sales write to
   the chapter’s own roster, and group email (Pro add-on) goes to the local chapter
   list.
 * **Your organization owns the data** — roster sheets stay in your own Google account
   rather than a hosted membership database. Chapter mode requires the separately
   licensed Chapters add-on on participating chapter sites.

Chapter mode is provided by the **GSheet Membership — Chapters** add-on; the free
plugin covers everything a single-roster organization needs.

### External services

This plugin connects to the following third-party services to provide its core functionality:

**Google Sheets API and Google OAuth2** (developers.google.com)
 Used to read and
write membership data to your private Google Sheet, and to authenticate as your 
Google Service Account. When a page’s access is checked, when a purchase is recorded,
or when you save settings and test the connection, the plugin sends: your Service
Account’s signed authentication request to https://oauth2.googleapis.com/token, 
and membership-related row data (emails, membership codes, and — depending on which
features you use — names, addresses, and purchase details) to https://sheets.googleapis.
com. This only occurs because you have configured a Google Service Account and Spreadsheet
ID; no data is sent to Google until you do so. Google APIs Terms of Service: https://
developers.google.com/terms Google API Services User Data Policy: https://developers.
google.com/terms/api-services-user-data-policy Google Privacy Policy: https://policies.
google.com/privacy

**Stripe** (stripe.com)
 Used to process membership purchases via Stripe Checkout.
Card details are entered on Stripe’s hosted checkout page, not this site. With the
free plugin’s Stripe Connect flow, the hosted GSheet Connect service creates the
Checkout Session and relays Stripe’s confirmation to the site; Pro can instead use
your own Stripe API keys and a direct Stripe webhook. Stripe is contacted only when
the gateway is enabled and connected or configured. Stripe Terms of Service: https://
stripe.com/legal/ssa Stripe Privacy Policy: https://stripe.com/privacy

**GSheet Connect** (access-manager-pro.replit.app) — Stripe Connect mode
 The plugin’s
hosted payment service is used for Stripe Connect sales, including on a Pro site
until its own Stripe keys are saved for the active mode. It handles “Connect with
Stripe” onboarding, creates the Stripe Checkout Session for each purchase (applying
the disclosed platform commission), processes refunds you issue from the dashboard,
and relays Stripe’s payment confirmations back to your site. The plugin sends: your
connected Stripe account ID, the purchase amount, currency and description, the 
buyer’s email address, and your site’s webhook address to https://access-manager-
pro.replit.app. The service does not keep a membership roster; payment processing
itself is performed by Stripe. A site using its own Stripe API keys instead of Connect
does not use this service for checkout. Service overview: https://access-manager-
pro.replit.app

**PayPal** (paypal.com) — requires the GSheet Membership Pro add-on
 Used to process
membership purchases via PayPal Checkout as an alternative to Stripe. Only active
if you install the Pro add-on and configure your PayPal credentials; the free plugin
does not send any data to PayPal on its own. PayPal User Agreement: https://www.
paypal.com/us/legalhub/paypal/useragreement-full PayPal Privacy Statement: https://
www.paypal.com/us/legalhub/paypal/privacy-full

**Mailchimp** (mailchimp.com) — requires the GSheet Membership Pro add-on
 Optional
campaign delivery and roster-sync method in Pro. With an API key and selected audience,
classic sends sync the current membership filter; segments-mode sends sync the full
roster and target a segment. The optional Sync Roster action updates the audience
without sending a campaign. Pro sends member email addresses, names, and optional
personalized link fields from the Google Sheet to https://.api.mailchimp.com to 
add, update, or archive audience contacts as appropriate; it does not override contacts
who unsubscribed or were cleaned. Only active when Pro is installed and Mailchimp
is configured; the free plugin sends no data to Mailchimp on its own. Mailchimp 
Standard Terms of Use: https://mailchimp.com/legal/terms/ Intuit Mailchimp Privacy
Statement: https://www.intuit.com/privacy/statement/

**EmailOctopus** (emailoctopus.com) — requires the GSheet Membership Pro add-on

An optional roster-sync destination, not a Group Email campaign-sending gateway.
If configured, Pro sends member email addresses, names, and optionally a personalized
attachment-link field to https://api.emailoctopus.com to update the selected list
without sending a campaign. The provider-card Sync Roster action syncs the full 
local recipient roster; Compose’s Sync Roster action uses the selected membership
filter. The free plugin sends no data to EmailOctopus on its own. EmailOctopus Terms
of Service: https://emailoctopus.com/legal/terms EmailOctopus Privacy Policy: https://
emailoctopus.com/legal/privacy

## Screenshots

[⌊Site Mode and Google Service Account — connect a private Google Sheet as the membership
roster.⌉⌊Site Mode and Google Service Account — connect a private Google Sheet as
the membership roster.⌉[

Site Mode and Google Service Account — connect a private Google Sheet as the membership
roster.

[⌊Main Roster — the organisation's spreadsheet ID and column mapping (email, membership
code, name, cache duration).⌉⌊Main Roster — the organisation's spreadsheet ID and
column mapping (email, membership code, name, cache duration).⌉[

Main Roster — the organisation’s spreadsheet ID and column mapping (email, membership
code, name, cache duration).

[⌊A private Google Sheet roster — email and membership-code columns support access
checks; additional columns can supply expiration dates, member details, and purchase
information.⌉⌊A private Google Sheet roster — email and membership-code columns 
support access checks; additional columns can supply expiration dates, member details,
and purchase information.⌉[

A private Google Sheet roster — email and membership-code columns support access
checks; additional columns can supply expiration dates, member details, and purchase
information.

[⌊Membership Expiration — Main Roster: choose Daily, Monthly, or Annual expiration
updates, then set the warning window, mode-specific grace period, and expired-access
message. Chapter sites can set separate local-roster rules.⌉⌊Membership Expiration—
Main Roster: choose Daily, Monthly, or Annual expiration updates, then set the warning
window, mode-specific grace period, and expired-access message. Chapter sites can
set separate local-roster rules.⌉[

Membership Expiration — Main Roster: choose Daily, Monthly, or Annual expiration
updates, then set the warning window, mode-specific grace period, and expired-access
message. Chapter sites can set separate local-roster rules.

[⌊Extra User-Info Fields — map additional sheet columns (address, phone, region)
for members to view and verify on the front end.⌉⌊Extra User-Info Fields — map additional
sheet columns (address, phone, region) for members to view and verify on the front
end.⌉[

Extra User-Info Fields — map additional sheet columns (address, phone, region) for
members to view and verify on the front end.

[⌊The Membership Access panel in the page editor sidebar — set the membership codes
required to view this page.⌉⌊The Membership Access panel in the page editor sidebar—
set the membership codes required to view this page.⌉[

The Membership Access panel in the page editor sidebar — set the membership codes
required to view this page.

[⌊The login gate turning away a visitor whose email is not on the roster.⌉⌊The login
gate turning away a visitor whose email is not on the roster.⌉[

The login gate turning away a visitor whose email is not on the roster.

[⌊Login Flow — pick the page that hosts your custom sign-in form.⌉⌊Login Flow — 
pick the page that hosts your custom sign-in form.⌉[

Login Flow — pick the page that hosts your custom sign-in form.

[⌊Front-end Appearance — force the login form and status panel to keep their original
colors and resize their text.⌉⌊Front-end Appearance — force the login form and status
panel to keep their original colors and resize their text.⌉[

Front-end Appearance — force the login form and status panel to keep their original
colors and resize their text.

[⌊Access Control Behaviour — exact vs. letters-mode code matching, and the message
shown when access is denied.⌉⌊Access Control Behaviour — exact vs. letters-mode 
code matching, and the message shown when access is denied.⌉[

Access Control Behaviour — exact vs. letters-mode code matching, and the message
shown when access is denied.

[⌊Signing in — a member enters their email to get started.⌉⌊Signing in — a member
enters their email to get started.⌉[

Signing in — a member enters their email to get started.

[⌊First-time sign-in — a 6-digit verification code is emailed and entered here.⌉⌊
First-time sign-in — a 6-digit verification code is emailed and entered here.⌉[

First-time sign-in — a 6-digit verification code is emailed and entered here.

[⌊After the code, a first-time member is prompted to create a password.⌉⌊After the
code, a first-time member is prompted to create a password.⌉[

After the code, a first-time member is prompted to create a password.

[⌊Membership status confirmed after sign-in — shown here right after a purchase,
but the same confirmation appears for any member signing in.⌉⌊Membership status 
confirmed after sign-in — shown here right after a purchase, but the same confirmation
appears for any member signing in.⌉[

Membership status confirmed after sign-in — shown here right after a purchase, but
the same confirmation appears for any member signing in.

[⌊Sales settings — the Pricing Matrix with Delivery, Duration and Postage pricing;
0 offers a free membership and a blank cell is unavailable.⌉⌊Sales settings — the
Pricing Matrix with Delivery, Duration and Postage pricing; 0 offers a free membership
and a blank cell is unavailable.⌉[

Sales settings — the Pricing Matrix with Delivery, Duration and Postage pricing;`
0` offers a free membership and a blank cell is unavailable.

[⌊The membership purchase picker a buyer sees, with live price updates.⌉⌊The membership
purchase picker a buyer sees, with live price updates.⌉[

The membership purchase picker a buyer sees, with live price updates.

[⌊The confirmation email a buyer receives after purchase.⌉⌊The confirmation email
a buyer receives after purchase.⌉[

The confirmation email a buyer receives after purchase.

[⌊The Membership Code Matrix — the stored code for every level and delivery-method
combination.⌉⌊The Membership Code Matrix — the stored code for every level and delivery-
method combination.⌉[

The Membership Code Matrix — the stored code for every level and delivery-method
combination.

[⌊Payments with Pro active — Stripe Connect account details and the optional PayPal
gateway; free-only sites use Stripe Connect without PayPal.⌉⌊Payments with Pro active—
Stripe Connect account details and the optional PayPal gateway; free-only sites 
use Stripe Connect without PayPal.⌉[

Payments with Pro active — Stripe Connect account details and the optional PayPal
gateway; free-only sites use Stripe Connect without PayPal.

[⌊Recent Purchases — purchase outcomes and available actions; sheet-write retry 
needs Pro, while refundable Stripe Connect sales can be refunded on free sites.⌉⌊
Recent Purchases — purchase outcomes and available actions; sheet-write retry needs
Pro, while refundable Stripe Connect sales can be refunded on free sites.⌉[

Recent Purchases — purchase outcomes and available actions; sheet-write retry needs
Pro, while refundable Stripe Connect sales can be refunded on free sites.

[⌊General Settings — currency, thank-you page, expiration-column override and the
new-member bonus cutoff used in Annual mode.⌉⌊General Settings — currency, thank-
you page, expiration-column override and the new-member bonus cutoff used in Annual
mode.⌉[

General Settings — currency, thank-you page, expiration-column override and the 
new-member bonus cutoff used in Annual mode.

[⌊Membership Level Names — map raw membership codes to the human-readable names 
members see.⌉⌊Membership Level Names — map raw membership codes to the human-readable
names members see.⌉[

Membership Level Names — map raw membership codes to the human-readable names members
see.

[⌊Email Verification (OTP) — code expiry and sender details for the verification
email.⌉⌊Email Verification (OTP) — code expiry and sender details for the verification
email.⌉[

Email Verification (OTP) — code expiry and sender details for the verification email.

## Installation

 1. Upload the `membership-google-sheets` folder to `/wp-content/plugins/` (or upload
    the ZIP under **Plugins → Add New → Upload Plugin**).
 2. Activate the plugin through the Plugins screen.
 3. Follow the Google setup steps below.
 4. Go to **Membership Access** (top-level menu in the WordPress admin sidebar) and
    configure the plugin.
 5. Click **Save & Test Main Roster** to verify the service account can read your sheet.

#### Google setup (~5 minutes)

 1. Go to https://console.cloud.google.com/ and create or select a project.
 2. Enable the **Google Sheets API** (APIs & Services  Library  search “Sheets”).
 3. Create a **Service Account** (IAM & Admin  Service Accounts  Create).
 4. Generate a **JSON key** for the service account (Keys tab  Add Key  JSON). Download
    the file.
 5. Open your Google Sheet  **Share**  add the service account email (looks like `name@
    project.iam.gserviceaccount.com`) as an **Editor** (required for the Sales feature
    and the Pro add-on’s Group Email; Viewer is enough if you only use page protection).
    The sheet stays private to everyone else.
 6. In **Membership Access** (top-level menu in the WordPress admin sidebar), paste
    the full contents of the JSON key file and enter your Spreadsheet ID.

When you click **Save & Test Main Roster**, the result identifies the exact service-
account email in use and checks authentication, spreadsheet access, available tabs,
the selected tab, row reading, and required column mappings in order. If a check
fails, the result names the Main roster and gives a specific correction without 
displaying the private key, access token, raw JSON, or Google response body.

For the Sheet (Tab) Name, leave the field blank to use the spreadsheet’s first tab
automatically. If an explicit tab name is missing, the connection result lists the
available tab names and tells you to choose one or clear the field.

#### Restricting pages

 1. Edit any page or post in WordPress.
 2. Find the **Membership Access** panel in the editor sidebar.
 3. Enter the required membership code(s), comma-separated (e.g. `GOLD, PLATINUM`).
 4. Save the page.

Codes are compared according to the Access Control Match Mode setting (exact match
by default, or “letters mode” — see Configuration reference below). Leave the field
blank for unrestricted access.

#### Configuration reference

 Setting Description Service Account JSON Full contents of the JSON key file downloaded
from Google Cloud Console Spreadsheet ID The ID from the spreadsheet URL (between`/
d/` and `/edit`) Sheet (Tab) Name Optional. Leave blank to use the spreadsheet’s
first tab automatically. Email Column Column letter containing email addresses (
default: A) Membership Code Column Column letter containing membership codes (default:
B) Header Row Number Row number of the header; data starts on the next row (default:
1) Cache Duration (seconds) How long to keep the encrypted whole-roster cache in
WordPress options (default: 300). Set 0 to disable roster caching. Expiration Mode
Daily (individual dates), Monthly, or Annual (covered-to year). Warning Window (
days) Days before expiration to start showing members a renewal warning (default:
14) Grace Period Continued access after expiration: days in Daily mode; calendar
months in Monthly and Annual modes. Expired-Access Message Message shown once a 
membership has expired and the grace period has passed Login Redirect URL Where 
to send unauthenticated visitors trying to access a restricted page Access-Denied
Redirect URL Where to redirect logged-in users without the required membership Access-
Denied Message Message to display instead of redirecting (if no redirect URL is 
set) Access Control Match Mode Exact match, or “letters mode,” where the member’s
code must contain every letter in the required set Custom Login Page The WordPress
page containing `[gsma_login_form]`; leave on Auto-detect to let the plugin find
it, or fall back to WordPress’s default login page

## FAQ

### Does the Google Sheet need to be shared publicly?

No — never. You only share it with the service account email address. It remains
completely private to all other users.

### What PHP extensions are required?

The **OpenSSL** extension is needed to sign the JWT for authentication. It is enabled
by default on virtually all shared and managed WordPress hosts (including WP Engine,
Kinsta, SiteGround, Flywheel, and others).

### Are administrators ever blocked?

No. Users with the `manage_options` capability always bypass all membership checks.

### Where is the Service Account JSON stored?

It is stored in your WordPress database (the `wp_options` table). With OpenSSL available,
the plugin encrypts it using this installation’s security salts; if encryption is
unavailable, the credential-saving routine can retain plaintext rather than replace
a working credential with unusable ciphertext. Check the encryption status on the
Access settings screen. It is never sent to the browser or written to a file by 
the plugin. Keep the salts and database backups secure.

### How quickly do membership changes take effect?

After the encrypted whole-roster cache expires (default 5 minutes / 300 seconds),
the next read fetches fresh data and writes a newly encrypted payload. Reduce the
cache duration in settings or click **Clear Cache** to force the next read to refresh.

### What happens if the Sheets API is unreachable?

The plugin logs an error and denies access as a safe default. Use **Save & Test 
Main Roster** in settings to verify connectivity.

### Signing in: verification code, then password

This works for **any member already on the roster**, whether they got there through
the plugin’s own purchase pathway or not — a name typed in manually, imported from
a spreadsheet, added after a mailed-in check, however your roster is maintained.
The sign-in flow doesn’t care how someone became a member, only that their email
is on the sheet. It’s part of the free plugin.

There’s no separate account-creation step. A member enters their email, and what
happens next depends on whether they’ve signed in before:

 * **First time signing in?** A 6-digit verification code is emailed to them. They
   enter the code and are prompted to create a password. From then on, that password
   signs them in directly.
 * **Already have a password?** The code step is skipped entirely — they go straight
   to a normal password sign-in.

(Pro’s **Authenticated mailbox sending**, below, improves deliverability of these
verification codes — it doesn’t add the sign-in flow itself.)

### Authenticated mailbox sending (Pro add-on)

With the **GSheet Membership Pro** add-on, you can connect a real mailbox (Google
sign-in or an App Password) on the main settings page and route login verification
codes, purchase confirmations and gift notices through it. A rejected verification
code falls back to the site’s normal mailer. Group Email can also use the authenticated
mailbox for batched sending, or use Mailchimp for campaign delivery. Correctly configured
mailbox authentication can improve deliverability; it does not guarantee that messages
avoid spam filters.

### Group Email: message your roster (Pro add-on)

With the **GSheet Membership Pro** add-on, the **Group Email** page in **Membership
Access** lets you compose a message and send it to your roster — everyone, or a 
subset filtered by membership code. Delivery can use the site’s batched mailer (
including an authenticated mailbox) or a connected **Mailchimp** audience. Mailchimp
can sync the filtered audience before a classic-mode send or target a segment of
the synced full roster. Chapter sites send Group Email to their local chapter roster
only, even if their access checks also include the main roster. Direct delivery 
and Mailchimp campaigns have different unsubscribe and reporting behavior; Pro includes
send history and delivery reports.

There are two ways to trigger a send: filling out the compose form yourself, or **
Email-in** — forwarding a single email to a dedicated mailbox, covered next.

### Email-in: trigger a group email by forwarding a message (Pro add-on)

Email-in is the second way to send a Group Email: instead of the compose form, you**
forward a message to a dedicated mailbox** (e.g. `blast@example.org`). The plugin
checks that mailbox on a schedule (IMAP); a message from an authorized sender that
meets the subject-keyword rule is queued under the same audience rules as the compose
form. The configured delivery method determines whether it goes through site mail
or Mailchimp.

### Setting it up

 1. Create a dedicated mailbox for intake (a Gmail / Google Workspace account works
    well). Don’t reuse a personal inbox — every unread message is examined.
 2. For Gmail / Google Workspace: enable IMAP (Gmail Settings  Forwarding and POP/IMAP),
    turn on 2-Step Verification for the account, and create an **App Password** (Google
    Account  Security  App passwords). The regular account password will NOT work over
    IMAP.
 3. In **Membership Access  Group Email**, fill in the Email-in card: host `imap.gmail.
    com`, port `993`, the mailbox address as username, and the App Password.
 4. Add the email addresses allowed to trigger a send (one per line) and — strongly
    recommended — a **required subject keyword**.
 5. Enable polling, click **Test connection**, then send yourself a trial: forward 
    a message from an allowlisted address (with the keyword in the subject) and click**
    Check mailbox now**.

### Why the subject keyword matters (spoofing)

The From: address of an email can be forged (spoofing) — an allowlist alone is not
sufficient protection for something that can email your whole membership. The required
subject keyword adds a second check; keep it private and choose a non-obvious phrase.
The keyword is stripped from the subject before delivery. This does not replace 
mailbox security or prevent an attacker who learns the keyword from sending a forged
message.

### Audience & attachments

 * Embed a code filter in the subject to target membership levels: `Meeting notes[
   codes: GOLD,SILVER]`. The tag is removed before delivery. Without a tag, the 
   membership code filter saved on the Group Email page is applied; if that filter
   is blank, all members receive the message. Works on both main and chapter sites.
 * **Chapter sites** send to the local chapter roster only — the code filter narrows
   within that roster, regardless of whether access checks also use the main roster.
 * Email-in attachments are uploaded to the media library. With Pro’s **File Delivery**
   link mode, every attachment becomes an expiring, recipient-bound link that opens
   without a login; PDFs may also be watermarked for the recipient. With link mode
   off, the first file is attached to site mail (or converted to a link if over 
   5 MB), while additional files use shared login-gated links. Mailchimp link mode
   supports up to five personalized file links per campaign; additional files are
   omitted with a notice.
 * Accepted and permanently skipped authorized messages are marked read and recorded
   to avoid duplicate sends. Unauthorized or unrelated messages are ignored and 
   left unread, with their IDs recorded so they are not repeatedly processed.

### Sales — sell memberships

The Sales half of **GSheet Membership** puts a configurable buy-membership picker
on any page and, on a successful payment, writes (or updates) the buyer’s row in
your private Google Sheet so they can immediately log in on your site.

 * **Three-dimensional picker** — buyers choose Delivery (Electronic / Print / Both)
   × Duration (1-Year and Lifetime included by default; the Pro add-on adds custom
   levels and multi-year durations) × Postage (Regular, First Class, Canada/Mexico,
   Other International), and the price updates live as they change selections.
 * **Discrete pricing matrix** — you set the exact price for every saleable combination
   in the admin. Enter `0` for a free membership that skips the gateway; leave a
   cell blank to make that combination unavailable.
 * **Stripe Checkout** — the free plugin uses Connect with Stripe and hosted onboarding,
   with no API keys to paste; Pro can use your own Stripe keys instead. Card details
   stay on Stripe’s hosted checkout page. Pro’s own-key settings provide a Test/
   Live switch; free Connect has no separate mode switch in WordPress.
 * **PayPal Standard Checkout** (requires the Pro add-on) — buyers can optionally
   pay via PayPal instead of (or in addition to) Stripe. Both gateways share the
   same sheet-upsert pipeline, purchase log, and retry UI.
 * **Sheet upsert** — on a verified webhook the plugin first tries to update the
   row that matches the buyer’s email; if there is no row, it appends one. Renewals
   extend an existing future expiration date instead of overwriting it.
 * **Reliable webhook pipeline** — per-event and per-email atomic locks prevent 
   duplicate writes from concurrent webhook redeliveries; a sheet-write failure 
   returns HTTP 500 so Stripe retries, and the admin sees a notice. The Pro add-
   on adds an admin retry control for failed sheet writes.
 * **Buyer confirmation email** — configurable subject and body with placeholders(
   first name, selection, amount, code, expiration, login URL, etc.).
 * **Admin direct-apply** — a separate `[gsms_admin_picker]` shortcode lets a site
   administrator apply a membership directly to the sheet **without taking a payment**,
   perfect for comp memberships, cheque / cash-in-hand orders, or back-filling an
   old purchase.
 * **Membership Level Names** — map the raw membership codes stored in your sheet
   to human-readable names, so a member who’s denied access sees a name like “Gold
   Member” instead of the raw code. Pro adds a level editor; copying Pro levels 
   to free-only storage requires an explicit administrator action.

### Requirements

 * PHP 7.4+ with the standard `openssl`, `hash`, and `mbstring` extensions (already
   enabled on every major WordPress host).
 * WordPress 5.9+.
 * The access features (above) configured with a working Service Account — the sales
   pipeline reuses the same sheet-writer.
 * A Stripe account for live or test payments (a PayPal developer account too, if
   you use the Pro add-on’s PayPal checkout).

### Selling setup

 1. Configure the Access settings first (see Installation above) and confirm the **
    Save & Test Main Roster** button succeeds.
 2. Go to **Membership Sales** in the WordPress admin sidebar. Every sub-page shows
    a “Setup” notice at the top reminding you which shortcode to put on which page.
 3. Fill in the **Pricing Matrix**, **Code Matrix**, and **Payments** sub-pages (see
    Configuration below).
 4. Create a page containing only the shortcode `[gsma_membership_purchase]` — that
    is the buy page.
 5. Create (or pick) a thank-you page and put `[gsma_user_info]` on it (that shortcode
    shows the buyer’s status with an inline login form when they’re not yet signed 
    in). Select that page on the **Settings** sub-page under **Thank-you page**.

### Shortcodes

 * `[gsma_login_form]` — renders the sign-in form (email, then a first-time verification
   code or a returning password) on the page you choose as the Custom Login Page.
   Independent of the Sales feature — every site can use it.
 * `[gsma_membership_purchase]` — renders the full picker (Delivery × Duration ×
   Postage) with live total and the **Buy membership** button. Place this on the
   page where you want to take payments.
 * `[gsms_admin_picker]` — admin-only variant of the picker that applies a membership
   directly to the sheet without any payment. Only site administrators (`manage_options`)
   see it; everyone else sees a polite notice.
 * `[gsma_user_info]` — place it on the thank-you page so buyers see their membership
   status (and an inline login form when they aren’t logged in).

### Admin sub-pages

 * **Pricing Matrix** — one row per Delivery, one column per Postage, repeated for
   each Duration. Type a price into a cell to make it saleable, including `0` for
   a free membership; leave it blank to make that combination unavailable. The **
   Membership Levels** block above the grid lets you choose which levels are currently
   offered for purchase.
 * **Code Matrix** — the membership code written into the buyer’s sheet row for 
   each combination. Every membership level gets its own row of codes. Levels beyond
   the built-in 1-Year and Lifetime, including multi-year durations, can be added
   with the Pro add-on; each new level gets its own Code Matrix row automatically.
   A warning is shown if any saleable cell is missing a code.
 * **Payments** — enable Stripe and use **Connect with Stripe** in the free plugin(
   no API keys or Stripe webhook secret to paste). With Pro, use your own Stripe
   keys instead and optionally enable PayPal, which has its own credentials and 
   webhook setup.
 * **General Settings** (sidebar label **Settings**) — currency, thank-you page 
   picker, optional override of the access settings’ expiration column, annual new-
   member bonus cutoff, buyer confirmation email templates, and option labels.
 * **Recent Purchases** — the last 200 purchase events with gateway and sheet status.
   Pro adds an inline **Retry** form for failed sheet writes and refunds for supported
   purchases; refundable Stripe Connect sales can also be refunded in the free plugin.

### Configuration: webhooks

**Stripe Connect (free plugin):** On the Payments page, click **Connect with Stripe**
and complete Stripe’s hosted onboarding. No Stripe API keys or direct webhook signing
secret are entered in the free plugin; payment confirmations are relayed through
the hosted Connect service.

**Direct Stripe API keys (Pro add-on only):** If you choose Pro’s own-key mode instead
of Connect, configure separate Test/Live keys on the Payments page and register 
its direct webhook:

 1. **Developers → Webhooks → Add endpoint**.
 2. Paste the **Stripe Webhook URL** shown on the Pro Payments page (ends in `/wp-json/
    gsma-sales/v1/stripe-webhook`).
 3. Subscribe to a single event: `checkout.session.completed`.
 4. Copy the resulting **Signing secret** (`whsec_...`) into Pro’s matching webhook
    field for that mode.

**PayPal** (requires the Pro add-on):

 1. In the PayPal developer dashboard → **My Apps & Credentials → your app → Add webhook**.
 2. Paste the **PayPal Webhook URL** shown on the plugin’s Payments page (ends in `/
    wp-json/gsma-sales/v1/paypal-webhook`).
 3. Subscribe to `CHECKOUT.ORDER.APPROVED` and `PAYMENT.CAPTURE.COMPLETED`.
 4. Copy the resulting **Webhook ID** into the matching field on the plugin’s Payments
    page.

### How to test without real money

The picker can be exercised without a payment gateway. A price of `0` also completes
a free membership without sending the buyer to Stripe or PayPal.

### 1. Picker UI / pricing layout (no gateway at all)

 * Fill in just the **Pricing Matrix** (real prices) and the **Code Matrix** (any
   short string like `TEST`).
 * Visit your buy page. The picker renders, the cheapest combination is preselected,
   and clicking different options updates the live total.
 * Leave a cell blank in the Pricing Matrix and reload — that postage / duration
   is greyed-out and unselectable, and the **Buy membership** button is disabled
   if the current selection has no price.

### 2. End-to-end with Stripe in TEST mode (Pro own-key setup)

 1. Sign up for a free Stripe account. No bank details are needed for test mode.
 2. Activate Pro, enable Stripe on **Payments**, select **Test** under **Your own Stripe
    keys (Pro)**, save your `pk_test_...` and `sk_test_...` keys, and register the 
    test-mode direct webhook as described above. The free Connect setup does not offer
    a Test/Live switch in the WordPress settings; do not use a test card against a 
    live connected account.
 3. Visit the buy page, fill in a test email/name, click **Buy membership**, and on
    the Stripe checkout page use the official test card:

 * **Card number:** 4242 4242 4242 4242
 * **Expiry:** any future date   **CVC:** any 3 digits   **ZIP:** any 5 digits

 1. After the redirect, open **Membership Sales → Recent Purchases** in the admin. 
    Once the payment confirmation is delivered, you should see a new entry with `paid`
    from Stripe and `inserted` (or `updated`) for the sheet.

### What happens if Stripe or PayPal sends the same event twice?

The first delivery is processed normally. Any duplicate of the same event is identified
and returns HTTP 200 with `status: duplicate` without writing to the sheet. Two 
concurrent deliveries for the same event are serialized via an atomic per-event 
lock; the loser receives HTTP 409 so the gateway retries it. From 1.7.13 each processed
event is stored as its own `wp_options` row with a 72-hour TTL — there is no fixed-
size eviction window that could cause old-but-recent events to fall off and be reprocessed.

### What happens if the Google Sheet write fails?

The plugin returns HTTP 500 to the gateway (so it retries delivery automatically),
records a “failed” entry on the Recent Purchases page, and shows a persistent admin
notice until the next successful write. The buyer confirmation email is **not** 
sent for a failed write — only a successful upsert triggers it. You can manually
rerun the upsert from the Recent Purchases page.

### Is the buyer’s payment ever lost if the sheet write fails?

No. The gateway has captured the payment regardless of what happens on your site.
The sheet write being deferred (and retried) means the buyer’s membership row catches
up automatically.

### Can lifetime memberships and renewals coexist?

Yes. Renewals extend the existing expiration date forward, so an early renewal never
shortens the buyer’s coverage. A lifetime purchase clears the expiration cell entirely.

### What if a buyer clicks Cancel at PayPal and then tries to pay again?

The cancelled checkout row is kept visible in Recent Purchases for auditing, but
it never blocks a retry. If the gateway somehow delivers a capture event for an 
order the cancel handler already marked cancelled, the finalisation pipeline now
detects and recovers that row automatically rather than treating it as already-done.

### Does the free plugin charge a fee on sales?

It depends on how you take card payments:

 * **Stripe Connect (free, the default since 3.5.0):** click **Connect with Stripe**
   on the Payments screen — no API keys to paste, Stripe hosts the onboarding. A**
   2% platform commission** is deducted from each card sale (in addition to Stripe’s
   own standard processing fees). The current rate is always shown on the Payments
   screen before you connect.
 * **Your own API keys (Pro add-on):** when Pro’s own Stripe keys are saved for 
   the active mode, direct-key checkout has a **0% platform fee**. Activating Pro
   alone does not switch a connected site away from Connect or remove its commission;
   deactivating Pro returns a connected site to Connect without losing its saved
   settings.
 * **Legacy free installs** that saved their own Stripe keys before 3.5.0 keep working
   with no commission; the keys remain a supported deprecation path until you choose
   to connect.
 * **PayPal** (a Pro feature) carries no platform commission either.

The commission is applied server-side by the plugin’s hosted Stripe Connect service
when the Checkout Session is created; the plugin itself never computes or transmits
the fee amount.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“GSheet Membership” is open source software. The following people have contributed
to this plugin.

Contributors

 *   [ gsheetplugins ](https://profiles.wordpress.org/gsheetplugins/)

[Translate “GSheet Membership” into your language.](https://translate.wordpress.org/projects/wp-plugins/membership-google-sheets)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/membership-google-sheets/),
check out the [SVN repository](https://plugins.svn.wordpress.org/membership-google-sheets/),
or subscribe to the [development log](https://plugins.trac.wordpress.org/log/membership-google-sheets/)
by [RSS](https://plugins.trac.wordpress.org/log/membership-google-sheets/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 4.9.0

 * Encrypt each cached main or chapter roster as a single payload in the WordPress
   database using a separate key derived from this site’s security salts.
 * Encrypt existing plaintext cache entries on first read without extending their
   expiry; discard cache entries if encryption fails.

#### 4.8.1

 * Existing Annual-mode sites now migrate their day-based grace setting to a calendar-
   month value that never shortens previously granted access.
 * The settings page warns before leaving with unsaved edits and shows a fixed Save
   Settings bar after a field changes.

#### 4.8.0

 * Annual expiration mode now uses the same month-based Grace Period field as Monthly
   mode. Daily mode continues to use days.
 * The single Grace Period control switches immediately between days and months 
   when the expiration mode changes.

#### 4.7.0

 * Replaced the separate annual-mode checkbox and Sales month-alignment controls
   with one roster-aware Daily, Monthly, or Annual expiration mode.
 * Monthly mode keeps active and recently lapsed renewals anchored to their prior
   expiration, limits deep-lapse backdating to half the configured grace window,
   and moves calculated dates forward to the first day of a month.
 * Existing sites using annual calendar-year memberships migrate automatically to
   Annual mode.

#### 4.6.13

 * Added an optional Sales expiration rule that extends new date-based memberships
   to the start of the next even-numbered or odd-numbered month.
 * The same adjustment can optionally apply to expired renewals after a configurable
   number of completed months past their prior expiration. Active renewals, lifetime
   memberships, annual calendar-year memberships, and rows without an expiration
   date are unchanged.

#### 4.6.12

 * Service-account JSON credentials are now encrypted at rest with authenticated
   encryption derived from this WordPress installation’s security salts. Existing
   plaintext credentials migrate automatically, and the Access settings screen reports
   their encryption status.
 * The credential-bearing settings option no longer autoloads on every WordPress
   request.

#### 4.6.11

 * Added an explicit security warning to the Google Service Account setup instructions:
   the downloaded JSON private-key file must never be shared or made public; only
   the service account email should be shared with the spreadsheet owner.

#### 4.6.10

 * Fixed chapter roster comparisons so main-roster rows without an email address
   can still be considered by the optional last-name review match. Email-less rows
   remain excluded from login, access, and ordinary member-profile lookups.

#### 4.6.9

 * Clarified that one Google Service Account covers every roster this site connects
   to. “Save & Test Main Roster” now notes when the connected sheet has Viewer-only
   access, so Sales and Group Email limitations are visible immediately. Blank-tab
   tests now show which tab was actually read.

#### 4.6.8

 * Replaced generic Google Sheets connection failures with staged, roster-specific
   diagnostics. Tests now identify malformed or incomplete service-account JSON,
   unusable private keys, Google authentication failures, spreadsheet access or 
   ID problems, missing explicit tabs with the available tab list, automatic first-
   tab selection, and required column-mapping errors. The report shows the non-secret
   service-account email that must be granted access while never showing private
   keys, tokens, raw JSON, roster rows, or Google response bodies.
 * Chapter connection tests and local-vs-main comparisons now identify the exact
   roster that needs correction instead of returning a generic comparison failure.

#### 4.6.7

 * Fixed blank/unknown Sheet tab names in Chapter roster comparisons and connection
   tests. Fresh main and Chapter configurations now truly leave the tab name blank
   so the first tab is discovered automatically. A renamed or recreated first tab
   is refreshed once instead of leaving comparison stuck on a stale cached title.
   Existing sites that still show the old “Sheet1” default can clear that field 
   once to opt into automatic first-tab discovery; explicit names still fail visibly
   if they are wrong.

#### 4.6.6

 * Fixed the Site Mode guidance so the Main and Chapter instructions update immediately
   when the mode selector changes. Chapter sites are now explicitly advised to use
   Viewer (read-only) access to the main roster so they cannot broadcast Group Email
   to the entire organisation.

#### 4.6.5

 * Tested with the latest stable WordPress release, 7.1.

#### 4.6.4

 * Fixed the Chapters promotion card for existing customers. Installed-but-inactive
   sites are directed to activate Chapters, and active sites with an expired, revoked,
   or missing license are directed to the Freemius Customer Portal instead of being
   asked to buy Chapters again.

#### 4.6.3

 * Clarified the Site Mode guidance: Chapter mode writes membership changes only
   to the local chapter roster and reads the main roster only for optional checks
   or comparisons; Main mode requires Editor access to the main roster for membership
   changes and Group Email, which can target only that authorized main roster.

#### 4.6.2

 * Added a secure Chapters add-on purchase link to the free plugin’s Access promotion.
   When Chapters is installed, the link uses its existing Freemius upgrade URL; 
   free-only sites use the verified public checkout for product 37810, plan 62888.

#### 4.6.1

 * Fixed an admin-screen fatal for sites where Pro is active but its license is 
   not currently valid. The GSheet Membership Pro card now correctly links existing
   customers to the Freemius Customer Portal.

#### 4.6.0

 * Added a clear, state-aware GSheet Membership Pro upgrade path to the Access and
   Sales admin screens. Free sites see the verified Pro checkout link, installed-
   but-inactive sites are directed to activate Pro, and licensed Pro sites see the
   active state instead of purchase prompts.
 * Added read-only Pro previews for authenticated mailbox sending, PDF watermarks,
   editable membership levels, and PayPal. Preview controls never save Pro-only 
   settings.

#### 4.5.9

 * WordPress.org listing refresh: broadened the discovery tags and branch/chapter
   wording, gave Group Email its own FAQ entry with Email-in as the forwarding trigger,
   expanded the screenshot gallery, and clarified Pro versus the separate Chapters
   add-on in both banner sizes. No functional changes.

#### 4.5.8

 * Multi-address roster cells now split on any separator: a second email address
   added on a new line inside the cell, or separated only by a space, is now recognized.
   Previously only commas and semicolons split, so an address on its own line silently
   fused with its neighbor and disappeared from logins, rosters, and email lists.

#### 4.5.7

 * Escaped result-box output at the point it’s printed, rather than relying on callers
   to have already done so, resolving a Plugin Check finding. No visible change.

#### 4.5.6

 * Action-result boxes can now be placed next to the exact form or buttons that 
   produced them: a result may carry a placement tag, and the page renders tagged
   results at the matching spot (used by the Pro Group Email send/test buttons).
   Untagged results keep rendering under the page title.

#### 4.5.5

 * Action results and save confirmations now appear inline on the plugin’s own admin
   pages — directly under the page title, next to the forms that triggered them —
   instead of at the very top of the admin screen.

#### 4.5.4

 * Admin feedback (save confirmations, action results, upload results, the Stripe
   connection status and the webhook-failure warning) is now shown in the plugin’s
   own message boxes instead of standard WordPress notice banners. Admin-cleanup
   and white-label plugins commonly hide or relocate every standard notice banner,
   which silently swallowed this feedback on affected sites.

#### 4.5.3

 * Admin notices (save confirmations, error reports, action results shown after 
   a redirect) now survive hosts whose object cache drops transients: each notice
   is also stored as a short-lived option and read from there first, so redirect
   round-trip feedback no longer disappears silently.

#### 4.5.2

 * Reworded comments and documentation describing what the Pro add-on supplies, 
   consistently phrased as capability it adds on top of the free plugin, never as
   something the free plugin restricts or withholds. No functional changes.

#### 4.5.1

 * Free-checkout polish: when a free ($0) signup can’t be recorded, the buyer now
   always sees the generic translated failure message — the specific reason stays
   in the admin purchase log, matching every other checkout failure. Internal documentation
   now lists the ‘free’ gateway.

#### 4.5.0

 * Free ($0) memberships are now supported: enter 0 in a Pricing Matrix cell to 
   offer that combination at no charge. Buyers who pick it skip the payment step
   entirely — the membership is recorded and the confirmation email sent immediately,
   and the picker shows “Free” instead of a zero amount. A blank cell still means
   not-for-sale.

#### 4.4.2

 * The Membership Levels block on the Pricing Matrix screen is now its own form 
   with a “Save Levels” button for choosing which levels are offered for purchase.
   The price grid below keeps its own Save button for prices only.

#### 4.4.1

 * The plugin no longer stores any Stripe API keys of its own — card payments on
   the free plugin always run through the hosted “Connect with Stripe” onboarding.
   Sites using their own Stripe keys via the Pro add-on are unaffected: the add-
   on now stores those keys itself. The PayPal enable toggle likewise moved to the
   Pro add-on’s settings defaults — the free plugin no longer defines any PayPal
   setting.

#### 4.3.6

 * A fresh install’s two default membership levels are now 1-Year and Lifetime (
   previously 1-Year and 3-Year). Existing sites are unaffected — the defaults only
   apply when no levels have been saved yet. Pairs with 4.3.5: a Lifetime member
   on a stock free install can change their delivery/postage at the correct price
   differential, no Pro add-on required.

#### 4.3.5

 * Existing members of a non-expiring level can now change their delivery/postage
   option without the Pro add-on: the plugin computes the price differential itself
   and charges exactly that. Same-option and lower-priced requests are declined 
   with clear messages. The Pro add-on’s role narrows to gift-upgrade validation.

#### 4.3.4

 * Readme: added a Mailchimp entry to the External services disclosure section (
   Pro’s optional Group Email delivery method). No functional changes.

#### 4.3.3

 * Wording: the plugin header description now reflects the current Pro feature set(
   level editor, Mailchimp delivery option, authenticated mailbox sending, PDF watermarks,
   large-file links). No functional changes.

#### 4.3.2

 * Wording: the Pro add-on summaries (settings-page teaser card and readme) now 
   mention the optional Mailchimp delivery for Group Email. No functional changes.

#### 4.3.1

 * Wording: the Pro teaser card on the Access settings page now describes the Pro
   add-on’s level editor instead of the outdated “multi-year and Lifetime durations”
   line (3-Year is included in the free plugin as of 4.3.0).

#### 4.3.0

 * A fresh install now ships two ready-to-sell membership levels, 1-Year and 3-Year.
   Existing sites keep their current levels unchanged.
 * Wording corrections across the Pricing Matrix screen and readme: the Pro add-
   on’s boundary is a level editor (create, rename, remove, redefine levels, including
   Lifetime) — not a level count.

#### 4.2.17

 * Plugin Check compliance: annotated the intentional use of WordPress’s DONOTCACHEPAGE
   caching convention on the login page so it is no longer flagged as an unprefixed
   plugin constant. No functional changes.

#### 4.2.16

 * Wording refinements on the Pricing Matrix screen.

Earlier entries are in changelog.txt inside the plugin.

## Meta

 *  Version **4.9.0**
 *  Last updated **16 часа ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 5.9 or higher **
 *  Tested up to **7.1.2**
 *  PHP version ** 7.4 or higher **
 *  Language
 * [English (US)](https://wordpress.org/plugins/membership-google-sheets/)
 * Tags
 * [association](https://mk.wordpress.org/plugins/tags/association/)[branches](https://mk.wordpress.org/plugins/tags/branches/)
   [Google Sheets](https://mk.wordpress.org/plugins/tags/google-sheets/)[membership](https://mk.wordpress.org/plugins/tags/membership/)
   [stripe](https://mk.wordpress.org/plugins/tags/stripe/)
 *  [Advanced View](https://mk.wordpress.org/plugins/membership-google-sheets/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/membership-google-sheets/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/membership-google-sheets/reviews/)

## Contributors

 *   [ gsheetplugins ](https://profiles.wordpress.org/gsheetplugins/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/membership-google-sheets/)