Title: GSheet Membership
Author: gsheetplugins
Published: <strong>August 20, 2026</strong>
Last modified: August 21, 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.5.9.zip)

 * [Details](https://wordpress.org/plugins/membership-google-sheets/#description)
 * [Reviews](https://wordpress.org/plugins/membership-google-sheets/#reviews)
 *  [Installation](https://wordpress.org/plugins/membership-google-sheets/#installation)
 * [Development](https://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.
 * **Caching** — sheet data and OAuth2 access tokens are cached as WordPress transients.
 * **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. Credentials are stored securely in your
WordPress database and are never exposed in the browser or source code.

#### 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.

### 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.
 * **No per-chapter SaaS fees** — association platforms typically charge every chapter
   a monthly subscription and hold your member data. Here the data stays in your
   own Google Sheets and each chapter site is just WordPress.

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.
When a buyer completes a purchase, their payment is handled entirely on Stripe’s
hosted checkout page (card details never pass through this site); the plugin sends
the buyer’s selection and price to Stripe to create the Checkout Session, and receives
a webhook confirming payment so it can update your Google Sheet. This only occurs
when Stripe is enabled and configured with your API keys. Stripe Terms of Service:
https://stripe.com/legal/ssa Stripe Privacy Policy: https://stripe.com/privacy

**GSheet Connect** (access-manager-pro.replit.app) — free tier only
 The plugin’s
hosted payment service, used when you take card payments without the Pro add-on.
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
at https://access-manager-pro.replit.app. The service stores no member data; payment
processing itself is performed by Stripe. This only occurs after you click “Connect
with Stripe” — sites using their own Stripe API keys (including all Pro sites) never
contact this service. 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
delivery method for the Group Email feature. When you choose Mailchimp delivery,
connect an API key and select an audience, each send first syncs that Mailchimp 
audience to your current membership filter — the plugin sends member email addresses
and names from your Google Sheet to https://.api.mailchimp.com to add, update, or
archive audience members — and then creates and sends the campaign through Mailchimp.
Only active if you install the Pro add-on, choose Mailchimp as the delivery method,
and configure your Mailchimp API key and audience; the free plugin does not send
any 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/

## 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 — only the Email and Membership columns matter to
the plugin, but your sheet can carry whatever else you track alongside them (name,
expiration date, address, phone, even international members).⌉⌊A private Google 
Sheet roster — only the Email and Membership columns matter to the plugin, but your
sheet can carry whatever else you track alongside them (name, expiration date, address,
phone, even international members).⌉[

A private Google Sheet roster — only the Email and Membership columns matter to 
the plugin, but your sheet can carry whatever else you track alongside them (name,
expiration date, address, phone, even international members).

[⌊Membership Expiration — Main Roster: warning window, grace period, and the message
shown once access lapses.⌉⌊Membership Expiration — Main Roster: warning window, 
grace period, and the message shown once access lapses.⌉[

Membership Expiration — Main Roster: warning window, grace period, and the message
shown once access lapses.

[⌊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 — only the Email and Membership
columns matter to the plugin, required membership codes for that page.⌉⌊The Membership
Access panel in the page editor sidebar — only the Email and Membership columns 
matter to the plugin, required membership codes for that page.⌉[

The Membership Access panel in the page editor sidebar — only the Email and Membership
columns matter to the plugin, required membership codes for that 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.⌉⌊
Sales settings — the Pricing Matrix with Delivery, Duration and Postage pricing.⌉[

Sales settings — the Pricing Matrix with Delivery, Duration and Postage pricing.

[⌊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 — enable Stripe and/or PayPal for the purchase picker (PayPal requires
the Pro add-on).⌉⌊Payments — enable Stripe and/or PayPal for the purchase picker(
PayPal requires the Pro add-on).⌉[

Payments — enable Stripe and/or PayPal for the purchase picker (PayPal requires 
the Pro add-on).

[⌊Recent Purchases — a live log of every sale, with refund and retry actions.⌉⌊Recent
Purchases — a live log of every sale, with refund and retry actions.⌉[

Recent Purchases — a live log of every sale, with refund and retry actions.

[⌊Sales general settings — currency code and the thank-you page shown after a successful
purchase.⌉⌊Sales general settings — currency code and the thank-you page shown after
a successful purchase.⌉[

Sales general settings — currency code and the thank-you page shown after a successful
purchase.

[⌊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 Connection** 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.

#### 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 The tab name inside the spreadsheet (default: 
Sheet1) 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 cache the sheet data locally (default: 300). Set 0 to disable.
Warning Window (days) Days before expiration to start showing members a renewal 
warning (default: 14) Grace Period (days) Days of continued access after expiration
before it’s revoked (default: 10) 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), protected by the
same security as your WordPress installation. It is never sent to the browser or
written to a file.

### How quickly do membership changes take effect?

After the cache expires (default 5 minutes / 300 seconds). Reduce the cache duration
in settings or click **Clear Cache** to force an immediate refresh.

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

The plugin logs an error and denies access as a safe default. Use **Save & Test 
Connection** 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 send **ALL plugin email**—
login verification codes, purchase confirmations, gift notices and group emails —
from that account. Because mail truly originates from the mailbox, SPF/DKIM/DMARC
align and messages stop landing in spam, with no separate SMTP plugin needed.

### 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 runs through the site’s own batched
mailer, or optionally through a connected **Mailchimp** account (the audience is
synced to your membership filter before every send). Chapter sites always send to
their own local roster only. Every send gets individual per-member delivery, unsubscribe
links, throttled background sending, and a per-recipient send log with retry/cancel.

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), and any message from an **authorized sender**
is queued to the roster through the exact same engine as the compose form — same
audience rules, same per-member delivery, unsubscribe links, and send log with retry/
cancel.

### 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 by anyone (spoofing) — an allowlist 
alone is not sufficient protection for something that can email your whole membership.
The required subject keyword acts as a shared secret: only people who know it can
trigger a send, even if they forge an authorized From: address. The keyword is stripped
from the subject before delivery, so members never see it.

### 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** always send to the local chapter roster only — the code filter
   narrows within that roster.
 * Attachments are uploaded to the media library. Files up to 5 MB are attached 
   to the outgoing email; larger files (and any additional attachments beyond the
   first) become download links in the message body.
 * Processed messages are marked read in the mailbox and their Message-IDs are recorded,
   so nothing is ever sent twice. Messages from unauthorized senders are marked 
   read, logged (when WP_DEBUG is on) and never sent.

### 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. Leave any cell blank to mark that combination as not for sale: 
   it is hidden / disabled in the picker and rejected server-side.
 * **Stripe Checkout** — redirect-style checkout (no card form on your site, lower
   PCI burden). Test mode and Live mode are stored side-by-side and toggled with
   one switch.
 * **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 + a per-row “
   Retry” button on the Recent Purchases page.
 * **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.

### 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 Connection** 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; 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/disable Stripe (and PayPal, with the Pro add-on); mode 
   switch (Test / Live for Stripe, Sandbox / Live for PayPal); keys and secrets 
   for each mode; webhook signing secret. The webhook URLs are shown here for paste-
   into-Stripe (and paste-into-PayPal with Pro).
 * **Settings** — currency, thank-you page picker, optional override of the access
   settings’ expiration column, buyer confirmation email templates, and option labels.
 * **Recent Purchases** — the last 200 webhook deliveries with their outcome (gateway
   status + sheet status). Failed sheet writes show an inline **Retry** form; completed
   Stripe and PayPal rows show a **Refund** control.

### Configuration: webhooks

**Stripe:**

 1. **Developers → Webhooks → Add endpoint**.
 2. Paste the **Stripe Webhook URL** shown on the plugin’s 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_...`) and paste it into **Webhook
    signing secret** on the plugin’s Payments page.

**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

Almost everything in this plugin can be exercised before you ever sign up for 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 (no real money)

 1. Sign up for a free Stripe account. No bank details are needed for test mode.
 2. On the plugin’s **Payments** page, enable Stripe, switch **Mode** to “Test”, paste
    your `pk_test_...` and `sk_test_...` keys, and save.
 3. Install the [Stripe CLI](https://stripe.com/docs/stripe-cli) on any laptop and 
    run:
 4. stripe listen –forward-to https://yoursite.example/wp-json/gsma-sales/v1/stripe-
    webhook
 5. The CLI prints a `whsec_...` signing secret — paste that into **Webhook signing
    secret** and save.
 6. Visit the buy page, fill in any 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. 
    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):** the **Pro add-on is commission-free** — Pro
   sites use their own Stripe keys with a **0% platform fee, permanently**. Deactivating
   Pro simply reverts the site to Connect mode without losing any 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.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.5.9**
 *  Last updated **11 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 5.9 or higher **
 *  Tested up to **7.0.4**
 *  PHP version ** 7.4 or higher **
 * Tags
 * [association](https://wordpress.org/plugins/tags/association/)[branches](https://wordpress.org/plugins/tags/branches/)
   [Google Sheets](https://wordpress.org/plugins/tags/google-sheets/)[membership](https://wordpress.org/plugins/tags/membership/)
   [stripe](https://wordpress.org/plugins/tags/stripe/)
 *  [Advanced View](https://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/)