Glass Register is a toolbox for developers to help create donation pages. It's a layer above the Stripe API that takes care of a lot of drudgery so that you can focus on the most important part: the donation experience.

In the past 3 years our donation pages have collected millions of dollars in donations across a dozen charitable organizations - the experience of which we've distilled into Glass Register's functionality. What we found we wanted the most was:

All of this is possible with Glass Register.

Building Forms

The way you enable a donation page for with Glass Register is designed to be as non-intrusive as possible: there's no generated HTML, mandatory CSS, or other ways to conflict with your favourite technology stack.

Build your HTML, CSS, and JavaScript any way you like, then: add two script includes, annotate HTML elements with data-* attributes, and call a single function.


The Basics

Given this form fragment:

  <label>First Name: <input></label>
  <label>Last Name: <input></label>

You would annotate it like this:

  <label>First Name: <input data-field="_firstName"></label>
  <label>Last Name: <input data-field="_lastName"></label>

(If you'd like to skip to seeing a fully working donation page, see the Example section)

That enables it to work with Glass Register. A few things to note:

Two other (optional) annotations are data-parse and data-validate. These are to indicate the JavaScript function that should be used to parse and validate the value contained in the element, respectively. We'll talk a bit more about those in a minute.


Warnings are what happens when the donor has pressed the "Donate" button, but the form contains invalid values. Here's the same form with a warning:

.warning {
  display: none;
} {
  display: block;
  <label>First Name: <input data-field="_firstName"
  <label>Last Name: <input data-field="_lastName"
  <div class="warning" data-warning-for="_firstName,_lastName">
    Please enter your complete first and last name (e.g. Jane Smith).

This is what happens during the validation phase of the form submission:

  1. The donor presses the "Donate" button
  2. Every element with a data-field attribute is parsed. Either using the default parser (suitable for text inputs and checkboxes), or a function specified in the data-parse attribute
  3. Any element with a data-validate attribute runs that parsed value through that validator

For each invalid field:

  1. The element with a matching data-warning-for is given the class gr-warning
  2. A callback is installed to remove that class as soon as any input is detected

The page will also scroll so that the topmost invalid field is within view (unless that feature is disabled). If the field itself is not the most logical place to scroll to, you can designate another element as the scroll target with a data-scroll-target-for attribute.

Using these attributes, the common requirement to display instructions when the form has been filled out incorrectly is easily fulfilled. There is more advanced functionality also available - we'll look into that a little later.


Warnings are essential, but it's a good practice to also display feedback while the donor is filling in the form. Both so that they aren't overwhelmed with errors at the very end, and also to encourage them to continue filling in the form. Here's how to do that:

.feedback {
  width: 40px;
  height: 40px;
} {
  background-image: url('check-mark.png');
} {
  background-image: url('question-mark.png');
  <label>First Name: <input data-field="_firstName"
  <label>Last Name: <input data-field="_lastName"
  <span class="feedback" data-feedback-for="_firstName,_lastName"></span>

When focus leaves a field, its contents will be validated using the specified function. Most often, this is the same function as used for the warnings, but if they're different, you can specify a function just for feedback via the data-feedback-validate attribute.

Depending on the results of the validation, the element with a matching data-feedback-for attribute will be given the class gr-valid or gr-invalid. In the above example, it will display an icon beside the field with those results. When the field regains and then loses focus, this process is repeated.

Note that you can also group feedback - they will behave intelligently (e.g. provide positive feedback unless any one of the group has been entered incorrectly).

Script Includes

That's just about it for changes to the HTML that you'll need to do - the one last modification is in the <head> element, by adding these script includes:

<script src=""></script>
<script src=""></script> 

Alternately, you can use your favourite script loader (e.g. RequireJS).

The first script pulls in all of the Stripe functions that we'll need, and the second loads a collection of functions and data tailored for your organization. That brings us to the JavaScript side of Glass Register.


The minimum integration that you'll need on the JavaScript side is to call a single function once the page is loaded:

var donateFn = grInit({
    frequency: 'oneTime',
    tokenCreationError: showError,
    chargeError: showError,
    chargeSuccess: chargeSuccess,
    preCharge: preCharge,

grInit() takes a single argument (a configuration object) and returns a function. Use that function like this (using jQuery syntax):

$('#form-submit').on('click', donateFn);

Where #form-submit is the donate button element - so when the user clicks on the "Donate" button that function will be invoked. The frequency key should be either "oneTime" or "monthly", depending on the kind of donation that the donor has selected. The rest are callbacks which are invoked at various stages in the form submission process.

Here's the complete sequence, and the associated callback(s) for each step:

Parsing and Validations

The parsing and validation are as described above. After this is (mostly - see below) complete, the optional postParseAndValidate callback is invoked. The argument is an object with three keys: values, valids, and allValid. The first is the results of the parsing pass (e.g. {_firstName: 'Jane', _lastName: 'Smith'}), the second is a series of booleans with the result of the validation pass (e.g. {_firstName: true, _lastName: false}). The final is just a convenience - it's true if every value in the valids object is true.

We said "mostly" complete above - that's because the assigning of the gr-warning class to invalid fields and the page scrolling haven't happened yet.

If you'd like to change the results of the parsing or validations, you can alter the object given in the argument and then return it from the callback. The warnings and scrolling will proceed with these new values.

You can also use this callback to completely replace the built-in warning system with your own: just don't use the attribute data-warning-for anywhere, set the key disableScrolling to true in the config object passed to grInit(), and then use this callback to implement your own warning system.

Alternately, there's a callback postWarningDisplay which is called only if there were form warnings, after those warnings have been displayed and the page scrolled. It has the same arguments as postParseAndValidate, but its return value is ignored.

Once all the fields are parsed and have passed validation, the form submission moves on to the next step: creating the Stripe credit card token.

Token Creation

Charging a credit card with Stripe is a two-step process - this is so, as a security measure, donors' credit card information is never sent to any non-Stripe server. The three special (required) fields _ccNum, _ccExp, and _ccCvc are sent to Stripe's servers, which create a one-time-use, unique token which represents the ability to create a charge on that credit card. Because it expires, and cannot be traced back to the credit card it represents, this is a lot safer than moving the actual credit card information around.

The (required) callback for this stage is called errorOnTokenCreate, and its argument is debugging information, including the response from Stripe (i.e. stripeResponse as to why it failed (e.g. the card was invalid)).

After the token is created, it along with the form data is sent to Glass Register's servers for processing.


After the token is created, but before the connection to the Glass Register's servers happens, an (optional) callback named preCharge is called. The first argument is the form as it will be sent to the servers, the second is an object similar to postParseAndValidate's argument, but with only the valids and allValid (the form data is split out into its own argument because the latter two tend not to be useful at this stage).

preCharge's purpose is twofold - to give you one last chance to make changes to the form before it's sent for processing, and to queue up any REST API calls that you want to make to your 3rd party integrations (e.g. sending a thank you email via a transactional email provider like SendGrid or Mailgun).

Accordingly, the return value from preCharge can optionally be an object with two keys: form and restApiCalls. The former is just the first argument (the form data) with whichever modifications you'd like to make, and the latter being the integration call details: specifically a list of objects with the keys call and extra. call is the name of which REST API call you'd like to make, and extra is any data that you'd like to pass to the REST API call which isn't already in the form data. Making a call is covered in much greater detail in the dashboard page of these docs.

Once the preCharge callback is returned from, the form, token, and REST API calls are sent off to Glass Register's servers for processing.


After the charge request has been processed, one of two required callbacks will be invoked: chargeSuccess or chargeError.

The argument for chargeSuccess is an object with two keys: transactionId and live. transactionId is the Stripe ID of the successful charge. live is whether the charge was "live", that is, not a test charge made with a test credit card number (see the "Testing" section below).

The argument for chargeError is an object with two keys: err and msg. The first is the error code, and the second is a human-readable explanation for what went wrong to help with debugging (it's still technical, though, so you probably don't want to display it directly to the donor). Here are error codes that you'll likely to run into:

unknown-frequencyThe frequency that you've set in the config is not one of "oneTime" or "monthly"
cvc-errorThe CVC code that the donor has entered does not match the card
card-errorStripe send back an error marked as "card_error", e.g. the card was declined, is not set up for this kind of transaction, etc.
processing-errorThis is any Stripe error that isn't related directly to the card (e.g. a network error, or a 500 error on their API)
charge-errorThis is an error on Glass Register's server. If you get this it will have been logged and one of our engineers will have been notified


If you use any of the Stripe test credit cards, it will put the charge processing into test mode (i.e. the charge will not be real). The transaction will fail or succeed depending entirely on the credit card number, and will be recorded in the "test" side of the Stripe dashboard.

Here are some useful card numbers:

4242 4242 4242 4242 → Always succeeds
4000 0000 0000 0127 → CVC check fails
4000 0000 0000 0002 → Card declined
4000 0000 0000 0119 → Triggers a processing error
4100 0000 0000 0019 → Marked as fraudulent

The CC expiry can be any date in the future, and the CVC can be any 3 or 4 digit number.

Special Fields

Any field name that starts with an underscore is reserved by Glass Register. This is the complete list of form fields which are treated (and stored) specially:


The special treatment is mostly for ensuring required fields are found, and for functions that require knowledge of the contents of the fields, like choosing the correct address to put on receipts.

Of special note: _amount is the donation amount in cents, not dollars, euros, etc.


Form Data

For reasons related to data consistency, durability, and searchability, the form data is stored alongside the transaction at Stripe in its metadata. This brings with it a few restrictions:

As well, there are some Glass Register-specific restrictions:

Browser Support

Glass Register supports the following browsers:

Error Handling

Most of the error handling happens in the callbacks, but the grInit() function also installs a global error handler that logs the exception, file, and line number of the error to the GR servers.

The function grLog(err, msg, payload) logs to the same place, and is available for whatever purpose that you'd like.

These logs, along with many others, can be queried in the dashboard.


The Glass Register Dashboard is has two "sides": one for administrators, and one for developers. If your user account is marked as developer you'll see both sides; administrators only see their side.

Administrator Dashboard

Transaction Downloads

This is where admins can download the donation transaction details. They can limit it by a date range, and re-arrange, add, and remove fields from the download. What fields show up, and their details (e.g. title and example values), is configured in the dev side of the dashboard.

You can save multiple configurations (i.e. field arrangements) with the "Add/Delete Configuration" buttons, switch between them using the tabs at the top, and save all of them with the "Save All Changes" button.

Downloads can be in either CSV or Excel format. There's also the choice between which of the Stripe "modes" to download the transactions from - the live or test modes.


Basic information about the organization can be configured here.

Of particular note is the "Statement Descriptor" field - this is what shows up on donor credit card statements. It is restricted to 22 alphanumeric characters.

eReceipt Customization

Glass Register provides an API that can be called to produce a PDF eReceipt for any one-time gift that has been made to your organization (we'll get into the details of that below).

This is where you can customize the content of those receipts. The images must be either JPEG or PNGs, and the text can be HTML with the following tags: <p>, <br>, <b>, <i>.

Click "Preview Receipt" and you'll see a receipt generated using the form data from the most recent test donation made to your organization.

Checkout Data

The purpose of this section is to allow for admins to alter the behaviour of the donation pages without having to involve the developers. It's a very flexible system - we'll explain it fully below in the dev side of the dashboard.

Manage Users

Here you can add and remove users for the organization. Currently all users have the same privileges - currently the only difference between devs and admins is that admins cannot see the dev side of the dashboard.

Any user can add and remove any other user. Once a user has been added, they will receive an email asking them to set their password in order to log in.

Developer Dashboard


This is where you can set configuration that's more technical - currently it's the Stripe keys.

Transaction Downloads

Like Checkout Data, this is a dev mirror image of the admin section. For each field that appears in their section, you can change the visibility and appearance of that field here.

For each field there are three things you can configure: the title (this is also what ends up in the spreadsheet header), an example value, and whether it's visible on the admin side.

You can add (within the restrictions listed in the Donation Form section) any fields that you like to a donation form. If you'd like those fields to be downloadable in the admin side of Transaction Downloads, you can add it via the form at the bottom of this section.

Event Log

Everything that happens on the Glass Register servers is logged. You can view all of those logs in this section.

Payload is data that is associated with the error: for instance if a donation failed, the log entry will not only say what when wrong, it will also keep all the related data (the form data, the REST API calls, etc.) in the payload, so you can figure out exactly went wrong.

The search function is based on RE2, a regular expression library designed to run in linear time, so you can search for extremely specific things.

Glass Register API

eReceipt Download API

Here's an example call using CURL:

curl -d @callArgs.json

The file callArgs contains a JSON object which is sent as the POST payload, it looks like this:

  "clientId": "sample",
  "email": "",
  "chargeId": "ch_1AF87g2hHuDMwXxgpkhoR8VH"

The charge ID is available in the first argument of the chargeSuccess callback on the donation page, or in in the values provided to the REST API calls (which can then be used, for example, as a substitution in an email template).

The response will be either the PDF file, or a JSON object with an err key with the error code and a msg key with a more friendly (but still technical) description for debugging.

The requirement for both the transaction ID and the email address is to stop people from guessing one or the other and accessing receipts that they are not supposed to.

Often the way this API is used is as part of a page which is linked to from a thank you email, and from the thank you page after the donor has given a gift.

Example Donation Page

A fully functioning donation page can be found here. Enter the credit card number 4242 4242 4242 4242 and any CVC and future expiry data to submit a "demo donation".

View source if you'd like to see how a complete Glass Register donation page looks from a code perspective.