Pageclip Documentation
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.
Setup
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.
Sites
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,
example.com,localhost
will save data from forms onhttp://example.com
,http://www.example.com
, andhttp://localhost:3000
, but reject data fromhttp://www.unicorns.com
.
Forms
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="https://s.pageclip.co/v1/pageclip.js" 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="https://s.pageclip.co/v1/pageclip.css" 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="https://send.pageclip.co/{yourSiteKey}/{formName}" class="pageclip-form" method="post">
<!-- Add your inputs with your own as you normally would -->
</form>
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="https://send.pageclip.co/{yourSiteKey}/{formName}" className="pageclip-form" method="post">
<!-- text field -->
<input type="email" name="email" value="bob@omg.com"/>
<select name="heardFrom">
<option value="ph" selected>Product Hunt</option>
<option value="hn">Hacker News</option>
</select>
<!-- 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">
<span>Submit</span>
</button>
</form>
Will save the following payload:
{
email: 'bob@omg.com',
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 thereply-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="https://send.pageclip.co/{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="bob@omg.com"/>
<!-- 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">
<span>Submit</span>
</button>
</form>
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="https://send.pageclip.co/{yourSiteKey}">
<input type="text" name="name" value="Billy Jean"/>
<input type="text" name="email" value="billy@example.com"/>
<input type="submit" value="Send">
</form>
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.
<form>
...
<button class="button pageclip-form__submit" type="submit">
<span>Submit</span>
</button>
</form>
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.
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;
}
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="https://send.pageclip.co/{yourSiteKey}">
<input type="text" name="name" required />
<input type="text" name="email" required />
<input type="submit" value="Send">
</form>
HTML5 validation has a few warts, so we created an open source library called valid-form
that makes them nicer.
valid-form
code at https://github.com/Pageclip/valid-form- See it in action at http://valid-form.pageclip.co
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
https://send.pageclip.co/{yourSiteKey}
# e.g. This URL saves to the default form
https://send.pageclip.co/1WoHHQWPLiWnR7IzGKwW9PROHL2G51BP
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
https://send.pageclip.co/{yourSiteKey}/{formName}
# e.g. This URL saves to the 'newsletter' form
https://send.pageclip.co/1WoHHQWPLiWnR7IzGKwW9PROHL2G51BP/newsletter
Advanced integration (pageclip.js)
When https://s.pageclip.co/v1/pageclip.js
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
- returningfalse
from this will prevent submission to PagecliponResponse:Function
- returningfalse
from this will prevent showing thesuccessTemplate
to the usersuccessTemplate: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 finishederror:Error
-null
when the request succeeds;Object
with.message
on failure.response:Object
-{"data":"ok"}
var data = {
name: 'Billy Jean',
email: 'billy@example.com'
}
Pageclip.send('...yourSiteKey...', 'contact', data, function (error, response) {
console.log('saved?', !!error, '; response:', error || response)
})
3rd Party Integrations
Webhook
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
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.
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": "billyjoe@omg.com"
},
"site": {
"siteEid": "YD2jPJKGY1CedwKfvM5NcDCHm5bQMpZQ",
"name": "Test Site",
"slug": "test-site",
"domains": "localhost,omg.com"
},
"form": {
"bucketEid": "HutBkLTizLfF7YFtOftpEWFjMCLxhR3j",
"name": "contact-form",
"displayName": "Contact Form"
},
"item": {
"itemEid": "Wt3tiXgvsimZ7sUjnivxZG2AJtvHQjGi",
"payload": {
"name": "Roscoe Jones",
"email": "roscoejones@wowza.com"
}
}
}
REST API
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
. siteKey
s 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 siteKey
s apart by the prefix; API keys are prefixed with api_
.
# An example. API keys are prefixed with `api_`. Keep this key private!
api_FP1We0oW9OcXlVurbLMxk1iGQrtGBR1W
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
application/vnd.pageclip.v1+json
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, response.data) // => 200, [Item, Item]
}).then(() => {
return pageclip.fetch()
}).then((response) => {
console.log(response.status, response.data) // => 200, [Item, Item]
})
See the node API client GitHub page for documentation.
Authentication
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}: https://api.pageclip.co/...
# Example. curl will base64 encode the username by default
curl --user 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E: https://api.pageclip.co/...
Errors
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
}, ...]
}
Endpoints
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; whenformName
is omitted, it will save to thedefault
form.
{
form: 'formName',
data: [Item]
}
# Save one object
curl \
-X PUT \
-u 01rhIN80DZkv1aTwtvqI1Yqe6P0mUK2E:
-H 'Content-Type: application/vnd.pageclip.v1+json' \
-d '{"name":"bob"}' \
https://api.pageclip.co/data/newsletter
#=> {"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"}]' \
https://api.pageclip.co/data/newsletter
#=> {"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); whenformName
is omitted, it will retrieve items from thedefault
form.- Query parameters (optional)
dataType
-csv
orjson
(default)archived
-true
orfalse
. 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' \
https://api.pageclip.co/data/newsletter
#=> {"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,bob@omg.com'
}
Items
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
}
}