Pageclip Documentation

Pageclip is a simple way to save information from your website via forms or front-end JavaScript. That is, you can save data from your website without a server—Pageclip is your server.

Pageclip works great when you want the simplicity of a static website, but don't want to setup a server for a form or two. Capture leads on landing pages, use for contact forms, collecting emails for a newsletter, polls, surveys, etc.

Pageclip works anywhere you can put an HTML form. It works with any static site generator e.g. Jekyll, Hugo, Hexo, GitBook, Gatsby and all others. Pageclip also works on any hosting provider, from blog hosting providers like Wordpress, to static hosting providers like GitHub Pages or S3, to Platform-as-a-Service providers like Heroku and Docker, and any other method of hosting you have up your sleeve.


First thing's first: have a website where you have access to edit the HTML :)

Create a Pageclip account if you haven't already. Once you have created an account, Pageclip will ask you to setup a site.


A site is a website that has one or more forms you want to save to Pageclip. Give your site a name, and its domains.

  • Name - The name can be anything that helps you remember the site e.g. Personal Blog
  • Domains - Domains are a comma-separated list of domains (with or without subdomains) that can send data to this Pageclip site. For example,,localhost will save data from forms on,, and http://localhost:3000, but reject data from


If you have more than one form on your website that you want to store in Pageclip, you will need to create a named form in Pageclip's UI for each form on your site.

For example, if you have a contact form and a newsletter form, you would create two named forms in Pageclip: contact, and newsletter.

Website Integration

Pageclip provides an HTTPS endpoint that will accept form data for each site and form. After you have created a site in Pageclip, it will show you the code to place on your website.

Step 1: You place a script tag before the </body> tag. This script provides the Pageclip global. By default this script will look for all form.pageclip-form elements on the page, and wrap them.

<script src="" charset="utf-8"></script>

Step 2: Next, you add the stylesheet to the <head>. It provides a loading spinner on the submit button and a 'thank you' message shown to the user after they have submitted the form. Feel free to override it with your own styling.

<link rel="stylesheet" href="" media="screen">

Step 3: Create your form with the specified form element. The key pieces are the action= attribute and the class="pageclip-form" attribute.

<form action="{yourSiteKey}/{formName}" class="pageclip-form" method="post">
  <!-- Add your inputs with your own as you normally would -->

Add your own <input /> tags with your own form fields, taking care that they have a name="..." attribute. Only fields with a name attribute will be saved to Pageclip.

Using name and email fields, this code will produce the following form:


Note that the form is submitted asynchronously and shows a 'thank you' message to the user once successfully submitted.

What data is saved?

Any form <input>, <textarea>, or <select> element with a name attribute is saved into Pageclip. Some notes:

  • Naming inputs with square brackets (e.g. someName[aChildKey]) will save the input under a child object. This is useful for groups of things like checkboxes. e.g. {someName: {aChildKey: true}}
  • Checkboxes (<input type="checkbox">) are stored as booleans. When unchecked, the browser does not send the input in the request, so a checkbox value will either be true, or the key will not exist at all.
  • File inputs (<input type="file">) are ignored, and multipart requests will be rejected.

An example form:

<form action="{yourSiteKey}/{formName}" className="pageclip-form" method="post">

  <!-- text field -->
  <input type="email" name="email" value=""/>

  <select name="heardFrom">
    <option value="ph" selected>Product Hunt</option>
    <option value="hn">Hacker News</option>

  <!-- radio buttons -->
  <input type="radio" name="animal" value="tarantula" id="tarantula" checked/>
  <label htmlFor="tarantula">Tarantula</label>

  <input type="radio" name="animal" value="turtle" id="turtle"/>
  <label htmlFor="turtle">Turtle</label>

  <!-- checkboxes -->
  <input type="checkbox" name="likes[pizza]" id="pizza" checked/>
  <label htmlFor="pizza">Pizza 🍕</label>

  <input type="checkbox" name="likes[yams]" id="yams"/>
  <label htmlFor="yams">Sweet Potatos 🍠</label>

  <!-- hidden inputs -->
  <input type="hidden" name="version" value="v1" />
  <button class="pageclip-form__submit" type="button">

Will save the following payload:

  email: '',
  heardFrom: 'ph',
  animal: 'tarantula',
  likes: {
    pizza: true
  version: 'v1'

Special fields

Pageclip can send you an email for each form submission and there are two specially named fields that make those emails easier to use:

  • email - the user's email. This will be used in the reply-to field of the email that Pageclip sends you. So all you have to do is hit 'Reply' in your email client to respond to the user.
  • subject - This will set the subject of the email that Pageclip sends you for each submission.

These fields are useful when you setup contact forms. An example contact form:

<form action="{yourSiteKey}/{formName}" className="pageclip-form" method="post">

  <!-- This will be used in the email's reply-to field. Must have name="email" -->
  <input type="email" name="email" value=""/>

  <!-- This will be used in the email's subject. Must have name="subject" -->
  <input type="text" name="subject" value="Email subject"/>

  <textarea name="body">Email body</textarea>

  <button class="pageclip-form__submit" type="button">

Bare minimum integration

You do not need to use the default asynchronous submission behavior. The absolute minimum you need to do is have your form POST to your site URL:

<form method="post" action="{yourSiteKey}">
  <input type="text" name="name" value="Billy Jean"/>
  <input type="text" name="email" value=""/>
  <input type="submit" value="Send">

With this form, Pageclip will save the data, then redirect back to the originating page.

Using the loading spinner

Pageclip can overlay a loading spinner on your submit button with some special markup. You must have a <button> element with the .pageclip-form__submit and a <span> child.

  <button class="button pageclip-form__submit" type="submit">

Styling the spinner

By default the spinner is white. You can change it to black by adding the pageclip-form__submit--dark-loader class to the button.

default button
using the `dark-loader` class

You can specify a custom color by styling the border

.pageclip-form__submit::after {
  border-color: rgba(0, 255, 0, 0.8);
  border-left-color: blue;
very attractive custom loader

Loading spinner details

When a form is submitted, the <button> will go through a few CSS class states. These classes provide CSS animations.

  • .pageclip-form__submit--start-loading: fades the <span> text out, and pops the loader into view
  • .pageclip-form__submit--loading: spins the loader
  • .pageclip-form__submit--end-loading: hides the loader, and fades the <span> text back in.

Some notes:

  • If the inner <span> is not specified, it will just overlay the spinner on the text.
  • If an <input type="submit" /> is used, the loading spinner will not work. A button must be used as the loading spinner is rendered in the ::after pseudo element.
  • The start and animations can be removed from the button. If animations do not exist, the JS will detect it and only add the .pageclip-form__submit--loading class to the button.

Form Validation

Pageclip works with any form validation library or scheme. If you have a favorite, just use that!

If you are unsure, we recommend using HTML5 validation that is built into the browser. HTML5 validation is very rich these days, and has good browser support. For more info, check out our blog post on HTML5 form validation.

A basic example enforcing required attributes:

<!-- Note the `required` attributes in inputs! -->
<form method="post" action="{yourSiteKey}">
  <input type="text" name="name" required />
  <input type="text" name="email" required />
  <input type="submit" value="Send">

HTML5 validation has a few warts, so we created an open source library called valid-form that makes them nicer.

Other HTML5 Form Validation Resources

Saving data to a specific form

By default, your site URL will save data to the 'default' form

# Saves to the default form{yourSiteKey}

# e.g. This URL saves to the default form

After you setup a named form in the Pageclip UI, you can save to that named form by appending the formName onto the end of your site's URL:

# Saves to the default form{yourSiteKey}/{formName}

# e.g. This URL saves to the 'newsletter' form

Advanced integration (pageclip.js)

When is included on the page, a window.Pageclip object is created. With it, you can hook into form submission, or send data to pageclip without a form at all.

Pageclip.form(form[, options])

Pageclip.form(...) wraps a form, submits the data to Pageclip asynchronously, and gives you a couple hooks into the lifecycle. You can use these for things like validation or displaying messages to the user. By default, any forms with class pageclip-form will be automatically wrapped with Pageclip.form(...).

  • form:HTMLFormElement - the <form /> element to submit to Pageclip. It must have an action pointing to your Pageclip site.
  • options:Object - (optional)
    • onSubmit:Function - returning false from this will prevent submission to Pageclip
    • onResponse:Function - returning false from this will prevent showing the successTemplate to the user
    • successTemplate:String
var form = document.querySelector('.pageclip-form')
Pageclip.form(form, {
  onSubmit: function (event) { },
  onResponse: function (error, response) { },
  successTemplate: '<span>Thank you!</span>'

Pageclip.send(siteKey, formName, data, callback)

Pageclip.send(...) allows you to send arbitrary JSON data via XHR to a Pageclip form.

  • siteKey:String - Your public siteKey; a unique token give to each site.
  • formName:String - The name of the form to save to. If you wish to send to a default form, formName must be 'default'.
  • data:Object - The actual data to save. JSON.stringify() will be called on this object before submission.
  • callback:Function - Called when submission is finished
    • error:Error - null when the request succeeds; Object with .message on failure.
    • response:Object - {"data":"ok"}
var data = {
  name: 'Billy Jean',
  email: ''
Pageclip.send('...yourSiteKey...', 'contact', data, function (error, response) {
  console.log('saved?', !!error, '; response:', error || response)

3rd Party Integrations


You can set up Pageclip to call a URL on your own server for each form submission. Pageclip will make an HTTP POST request to the URL of your choice with JSON data about the new item. To be clear, a webhook will be called each time a user submits a form.

Go to the Integrations tab on your site's Pageclip UI, and click Set Webhook

Set Webhook

Webhook Token

In each POST request Pageclip makes to your server, it will include a token in the request body. On your server, you can use this token to verify that the request is from Pageclip.

  "action": "newItem",
  "isTest": true,
  "token": "99c59ojxCCFfwwsLtyZUppcHbIyGrfXd",

You can copy this token in the Pageclip UI once you have set your URL. We advise you keep this token in an environment variable on your server.

Webhook UI

On your server, you might do something like this:

server.handlePost('/pageclip-webhook', (bodyJSON) => {
  if (bodyJSON.token === process.env.PAGECLIP_TOKEN) {
    // Do successful things
  } else {
    // Fail; probably sketchy!

Webhook Payload

Each webhook request will contain data about the organization, site, form, and of course, the new item. Some notes:

  • item.payload is the actual data the user submitted.
  • isTest will be true if the request is from clicking the Test Webhook button in the UI
  "action": "newItem",
  "isTest": true,
  "token": "99c59ojxCCFfwwsLtyZUppcHbIyGrfXd",
  "organization": {
    "organizationEid": "JyAmtMGNVAjWPg8RnyG5vJkSk4f9Ntf7",
    "name": "Test Org",
    "isPersonal": false,
    "billingEmail": ""
  "site": {
    "siteEid": "YD2jPJKGY1CedwKfvM5NcDCHm5bQMpZQ",
    "name": "Test Site",
    "slug": "test-site",
    "domains": "localhost,"
  "form": {
    "bucketEid": "HutBkLTizLfF7YFtOftpEWFjMCLxhR3j",
    "name": "contact-form",
    "displayName": "Contact Form"
  "item": {
    "itemEid": "Wt3tiXgvsimZ7sUjnivxZG2AJtvHQjGi",
    "payload": {
      "name": "Roscoe Jones",
      "email": ""


The REST API allows you to send and fetch data from a Pageclip site/form. You can use the API from a script, server, or anywhere other than a browser that can make HTTP requests.

API Keys

The API uses a private API key that is different from the siteKey. siteKeys are write only, while your API keys are read and write. It is important not to share your API keys or place them on your website. You can tell API keys and siteKeys apart by the prefix; API keys are prefixed with api_.

# An example. API keys are prefixed with `api_`. Keep this key private!

At this time, each site you create in Pageclip gets its own private API key. i.e. There is no single API key for your account that works across all of your sites.

API Version

The Pageclip REST API is currently version v1. Version is specified via the HTTP Accept header. Please use this value in the Accept header


If you do not set this value in the header, future API version changes could break your client.

API Clients

At this time, there is a node API client.

npm install --save pageclip
import Pageclip from 'pageclip'
const pageclipAPIKey = 'api_abc123abc123...'
const pageclip = new Pageclip(pageclipAPIKey)

pageclip.send({some: 'data'}).then((response) => {
  console.log(response.status, // => 200, [Item, Item]
}).then(() => {
  return pageclip.fetch()
}).then((response) => {
  console.log(response.status, // => 200, [Item, Item]

See the node API client GitHub page for documentation.


Pageclip uses HTTP Basic Auth. Your API key is sent as the username, and there is no password. The API key must be base64 encoded.

curl --user {yourAPIKey}:

# Example. curl will base64 encode the username by default
curl --user 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E:


API errors will return a JSON response with an errors key that contains an Array of objects:

  errors: [{
    message: 'Name is required',
    property: 'name' // If the error is associated with a property
  }, ...]


PUT /api/data/[formName]

Save JSON data to a form. Allows submitting one or many items.

Returns a JSON object with the form name, and an Array of the Items that were saved.

  • formName is optional; when formName is omitted, it will save to the default form.
  form: 'formName',
  data: [Item]
# Save one object
curl \
  -X PUT \
  -u 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E:
  -H 'Content-Type: application/vnd.pageclip.v1+json' \
  -d '{"name":"bob"}' \
#=> {"form": "newsletter", "data": [Item({name: 'bob'})]}

# Save multiple objects
curl \
  -X PUT \
  -u 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E:
  -H 'Content-Type: application/vnd.pageclip.v1+json' \
  -d '[{"name":"bob"}, {"name":"sally"}]' \
#=> {"form": "newsletter", "data": [Item({name: 'bob'}), Item({name: 'sally'})]}

GET /api/data/[formName]?[queryParameters]

Fetch items from a form.

Returns a JSON object with the form name, data type of data, and an Array of Items. At this time, GET returns all items in a form.

  • formName (optional); when formName is omitted, it will retrieve items from the default form.
  • Query parameters (optional)
    • dataType - csv or json (default)
    • archived - true or false. When false, it will return only unarchived items; when true, it will return archived items. By default it will return all items.
curl \
  -X GET \
  -u 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E:
  -H 'Accept: application/vnd.pageclip.v1+json' \
#=> {"form": "newsletter", "dataType": "json", "data": [Item, Item, ...]}

The output will be in the following shape

// /api/data/newsletter
  form: 'newsletter',
  dataType: 'json',
  data: [Item, Item]

If you request CSV data with the dataType query parameter, the output will be in the following shape

// /api/data/newsletter?dataType=csv
  form: 'newsletter',
  dataType: 'csv',
  data: 'name,email\nbob,'


All Item objects returned by the API will have the following shape:

  itemEid: 'abc123ABC123abc123abc123abc12345',
  createdAt: '1983-06-29T14:48:00Z', // ISO date
  archivedAt: null, // ISO date or null when not archived
  payload: {
    // the data from the user