Integrate SnipLink with Your Blog
Learn how to automatically create short links for every blog post, track social shares, and measure content performance across different platforms.
What You'll Build
By the end of this tutorial, your blog will have:
- Automatic short link generation - Create a short link every time you publish a post
- Social sharing buttons - Pre-populated share buttons with short links
- Click tracking - See which posts get the most social engagement
- UTM parameter automation - Automatically tag links for analytics
- Popular posts widget - Show trending posts based on click data
Prerequisites
- A SnipLink account with Pro plan or higher (sign up here)
- An API key with
links:writeandanalytics:readscopes - Your workspace ID
- A blog on WordPress, Next.js, or any static site generator
Choose Your Platform
Select the integration guide that matches your blogging platform:
WordPress
Custom plugin with admin UI for managing short links
Next.js
API routes and React components for modern blogs
Static Sites
Build-time generation for Hugo, Jekyll, Gatsby
Option 1: WordPress Integration
Create a custom WordPress plugin that automatically generates short links when you publish posts.
Step 1: Create the Plugin
Create a new folder wp-content/plugins/sniplink-integration/ with these files:
sniplink-integration.php
<?php
/**
* Plugin Name: SnipLink Integration
* Description: Automatically create short links for blog posts
* Version: 1.0
* Author: Your Name
*/
// Prevent direct access
if (!defined('ABSPATH')) exit;
class SnipLink_Integration {
private $api_key;
private $workspace_id;
private $api_base = 039;https://api.sniplink.co/v1';
public function __construct() {
// Get settings from WordPress options
$this->api_key = get_option('sniplink_api_key');
$this->workspace_id = get_option('sniplink_workspace_id');
// Hook into post publish
add_action('publish_post', array($this, 'create_short_link'), 10, 2);
// Add settings page
add_action('admin_menu', array($this, 'add_settings_page'));
add_action('admin_init', array($this, 'register_settings'));
// Add meta box to post editor
add_action('add_meta_boxes', array($this, 'add_meta_box'));
// Add shortcode for sharing buttons
add_shortcode('sniplink_share', array($this, 'share_buttons_shortcode'));
}
public function create_short_link($post_id, $post) {
// Skip if no API credentials
if (empty($this->api_key) || empty($this->workspace_id)) {
return;
}
// Skip if short link already exists
$existing_link = get_post_meta($post_id, '_sniplink_short_url', true);
if (!empty($existing_link)) {
return;
}
// Get post data
$post_url = get_permalink($post_id);
$post_slug = $post->post_name;
$post_title = $post->post_title;
// Create short link via API
$response = wp_remote_post(
"{$this->api_base}/workspaces/{$this->workspace_id}/links",
array(
'headers' => array(
'X-API-Key' => $this->api_key,
'Content-Type' => 'application/json',
),
'body' => json_encode(array(
'url' => $post_url,
'slug' => $post_slug,
'title' => $post_title,
'tags' => array('blog', 'wordpress'),
)),
)
);
if (is_wp_error($response)) {
error_log('SnipLink API Error: ' . $response->get_error_message());
return;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (isset($body['link']['shortUrl'])) {
// Save short URL to post meta
update_post_meta($post_id, '_sniplink_short_url', $body['link']['shortUrl']);
update_post_meta($post_id, '_sniplink_link_id', $body['link']['id']);
}
}
public function add_meta_box() {
add_meta_box(
'sniplink_meta_box',
'SnipLink Short URL',
array($this, 'render_meta_box'),
'post',
'side',
'high'
);
}
public function render_meta_box($post) {
$short_url = get_post_meta($post->ID, '_sniplink_short_url', true);
if ($short_url) {
echo '<p><strong>Short URL:</strong></p>';
echo '<input type="text" value="' . esc_attr($short_url) . '" readonly style="width: 100%;" />';
echo '<p><a href="' . esc_url($short_url) . '" target="_blank">Open Link</a></p>';
} else {
echo '<p>Short link will be created when you publish this post.</p>';
}
}
public function share_buttons_shortcode($atts) {
global $post;
$short_url = get_post_meta($post->ID, '_sniplink_short_url', true);
if (empty($short_url)) {
return '';
}
$title = urlencode(get_the_title());
$encoded_url = urlencode($short_url);
ob_start();
?>
<div class="sniplink-share-buttons">
<a href="https://twitter.com/intent/tweet?text=<?php echo $title; ?>&url=<?php echo $encoded_url; ?>"
target="_blank" class="share-btn twitter">
Share on Twitter
</a>
<a href="https://www.facebook.com/sharer/sharer.php?u=<?php echo $encoded_url; ?>"
target="_blank" class="share-btn facebook">
Share on Facebook
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url=<?php echo $encoded_url; ?>"
target="_blank" class="share-btn linkedin">
Share on LinkedIn
</a>
</div>
<style>
.sniplink-share-buttons {
display: flex;
gap: 10px;
margin: 20px 0;
}
.share-btn {
padding: 10px 20px;
border-radius: 5px;
color: white;
text-decoration: none;
font-size: 14px;
}
.share-btn.twitter { background: #1DA1F2; }
.share-btn.facebook { background: #4267B2; }
.share-btn.linkedin { background: #0077B5; }
</style>
<?php
return ob_get_clean();
}
public function add_settings_page() {
add_options_page(
'SnipLink Settings',
'SnipLink',
'manage_options',
'sniplink-settings',
array($this, 'render_settings_page')
);
}
public function register_settings() {
register_setting('sniplink_settings', 'sniplink_api_key');
register_setting('sniplink_settings', 'sniplink_workspace_id');
}
public function render_settings_page() {
?>
<div class="wrap">
<h1>SnipLink Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('sniplink_settings'); ?>
<table class="form-table">
<tr>
<th scope="row">API Key</th>
<td>
<input type="text" name="sniplink_api_key"
value="<?php echo esc_attr(get_option('sniplink_api_key')); ?>"
class="regular-text" />
<p class="description">Your SnipLink API key (starts with snip_live_)</p>
</td>
</tr>
<tr>
<th scope="row">Workspace ID</th>
<td>
<input type="text" name="sniplink_workspace_id"
value="<?php echo esc_attr(get_option('sniplink_workspace_id')); ?>"
class="regular-text" />
<p class="description">Your SnipLink workspace ID (starts with ws_)</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
}
// Initialize plugin
new SnipLink_Integration();Step 2: Activate the Plugin
- Upload the plugin folder to your WordPress installation
- Go to Plugins ā Installed Plugins
- Activate "SnipLink Integration"
- Go to Settings ā SnipLink and enter your API key and workspace ID
Step 3: Add Sharing Buttons to Posts
Add this shortcode to your blog post template or use it in individual posts:
[sniplink_share]Or add it to your theme template:
<?php echo do_shortcode('[sniplink_share]'); ?>Option 2: Next.js Blog Integration
For modern Next.js blogs, create API routes and React components.
Step 1: Create API Route
// pages/api/create-short-link.ts
import { NextApiRequest, NextApiResponse } from 039;next039;;
const API_BASE = 039;https://api.sniplink.co/v1';
const API_KEY = process.env.SNIPLINK_API_KEY!;
const WORKSPACE_ID = process.env.SNIPLINK_WORKSPACE_ID!;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 039;POST039;) {
return res.status(405).json({ error: 039;Method not allowed039; });
}
const { url, slug, title, tags } = req.body;
try {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/links`,
{
method: 039;POST039;,
headers: {
039;X-API-Key039;: API_KEY,
039;Content-Type039;: 039;application/json039;,
},
body: JSON.stringify({
url,
slug,
title,
tags: tags || [039;blog039;, 039;nextjs039;],
}),
}
);
if (!response.ok) {
const error = await response.json();
return res.status(response.status).json(error);
}
const data = await response.json();
res.status(200).json(data);
} catch (error) {
res.status(500).json({ error: 039;Failed to create short link039; });
}
}Step 2: Create Build-Time Script
Generate short links during the build process:
// scripts/generate-short-links.ts
import fs from 039;fs039;;
import path from 039;path039;;
import matter from 039;gray-matter039;;
const API_BASE = 039;https://api.sniplink.co/v1';
const API_KEY = process.env.SNIPLINK_API_KEY!;
const WORKSPACE_ID = process.env.SNIPLINK_WORKSPACE_ID!;
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!;
async function createShortLink(url: string, slug: string, title: string) {
const response = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/links`,
{
method: 039;POST039;,
headers: {
039;X-API-Key039;: API_KEY,
039;Content-Type039;: 039;application/json039;,
},
body: JSON.stringify({
url,
slug,
title,
tags: [039;blog039;],
}),
}
);
if (response.status === 409) {
// Link already exists, fetch it instead
const getResponse = await fetch(
`${API_BASE}/workspaces/${WORKSPACE_ID}/links?slug=${slug}`,
{
headers: { 039;X-API-Key039;: API_KEY },
}
);
const data = await getResponse.json();
return data.links[0];
}
if (!response.ok) {
throw new Error(`Failed to create link: ${response.statusText}`);
}
const data = await response.json();
return data.link;
}
async function main() {
const postsDirectory = path.join(process.cwd(), 039;content/posts039;);
const filenames = fs.readdirSync(postsDirectory);
const shortLinks: Record<string, string> = {};
for (const filename of filenames) {
if (!filename.endsWith(039;.md039;)) continue;
const filePath = path.join(postsDirectory, filename);
const fileContents = fs.readFileSync(filePath, 039;utf8039;);
const { data } = matter(fileContents);
const slug = filename.replace(/\.md$/, 039;039;);
const url = `${SITE_URL}/blog/${slug}`;
try {
const link = await createShortLink(url, slug, data.title);
shortLinks[slug] = link.shortUrl;
console.log(`ā Created short link for "${data.title}": ${link.shortUrl}`);
} catch (error) {
console.error(`ā Failed to create link for ${slug}:`, error);
}
}
// Save to JSON file
fs.writeFileSync(
path.join(process.cwd(), 039;data/short-links.json039;),
JSON.stringify(shortLinks, null, 2)
);
console.log(`\n⨠Generated ${Object.keys(shortLinks).length} short links`);
}
main();Run this script before building:
{
"scripts": {
"prebuild": "tsx scripts/generate-short-links.ts",
"build": "next build"
}
}Step 3: Create Share Buttons Component
// components/ShareButtons.tsx
039;use client039;;
import { useState } from 039;react039;;
interface ShareButtonsProps {
url: string;
title: string;
}
export function ShareButtons({ url, title }: ShareButtonsProps) {
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
const encodedUrl = encodeURIComponent(url);
const encodedTitle = encodeURIComponent(title);
return (
<div className="flex items-center gap-3">
<span className="text-sm font-medium text-gray-700">Share:</span>
<a
href={`https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-lg bg-[#1DA1F2] px-4 py-2 text-sm font-medium text-white hover:bg-[#1a8cd8]"
>
Twitter
</a>
<a
href={`https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-lg bg-[#4267B2] px-4 py-2 text-sm font-medium text-white hover:bg-[#365899]"
>
Facebook
</a>
<a
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center rounded-lg bg-[#0077B5] px-4 py-2 text-sm font-medium text-white hover:bg-[#006396]"
>
LinkedIn
</a>
<button
onClick={handleCopy}
className="inline-flex items-center rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
>
{copied ? 039;Copied!039; : 039;Copy Link039;}
</button>
</div>
);
}
// Usage in blog post:
// import shortLinks from '@/data/short-links.json';
//
// <ShareButtons
// url={shortLinks[slug] || `https://yourblog.com/blog/${slug}`}
// title={post.title}
// />Option 3: Static Site Integration
For Hugo, Jekyll, Gatsby, or other static site generators.
Hugo Example
Create a script that runs during build:
#!/bin/bash
# scripts/generate-short-links.sh
API_KEY="snip_live_your_api_key"
WORKSPACE_ID="ws_xxxxxxxxxxxxx"
SITE_URL="https://yourblog.com"
# Create data directory if it doesn't exist
mkdir -p data
# Initialize JSON file
echo "{" > data/shortlinks.json
# Loop through all blog posts
for file in content/posts/*.md; do
# Extract slug from filename
slug=$(basename "$file" .md)
# Extract title from frontmatter
title=$(grep "^title:" "$file" | cut -d':' -f2- | tr -d '"' | xargs)
# Create full URL
url="$SITE_URL/posts/$slug/"
# Call SnipLink API
response=$(curl -s -X POST "https://api.sniplink.co/v1/workspaces/$WORKSPACE_ID/links" \
-H "X-API-Key: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"$url\",
\"slug\": \"$slug\",
\"title\": \"$title\",
\"tags\": [\"blog\", \"hugo\"]
}")
# Extract short URL from response
shortUrl=$(echo $response | jq -r '.link.shortUrl')
# Add to JSON (skip last comma handling for simplicity)
echo " \"$slug\": \"$shortUrl\"," >> data/shortlinks.json
echo "Created: $shortUrl -> $title"
done
# Close JSON
echo "}" >> data/shortlinks.json
echo "Done! Short links saved to data/shortlinks.json"Make it executable and run before build:
chmod +x scripts/generate-short-links.sh
./scripts/generate-short-links.sh
hugoAccess short links in your Hugo templates:
<!-- layouts/partials/share-buttons.html -->
{{ $shortUrl := index .Site.Data.shortlinks .File.BaseFileName }}
<div class="share-buttons">
<a href="https://twitter.com/intent/tweet?text={{ .Title }}&url={{ $shortUrl }}"
target="_blank">
Share on Twitter
</a>
<a href="https://www.facebook.com/sharer/sharer.php?u={{ $shortUrl }}"
target="_blank">
Share on Facebook
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ $shortUrl }}"
target="_blank">
Share on LinkedIn
</a>
</div>Advanced: Popular Posts Widget
Show trending posts based on click analytics:
// components/PopularPosts.tsx
039;use client039;;
import { useState, useEffect } from 039;react039;;
import Link from 039;next/link039;;
interface Post {
slug: string;
title: string;
clicks: number;
}
export function PopularPosts() {
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchPopularPosts() {
try {
const response = await fetch(039;/api/popular-posts039;);
const data = await response.json();
setPosts(data.posts);
} catch (error) {
console.error(039;Failed to fetch popular posts:039;, error);
} finally {
setLoading(false);
}
}
fetchPopularPosts();
}, []);
if (loading) {
return <div>Loading popular posts...</div>;
}
return (
<div className="rounded-lg border border-gray-200 bg-white p-6">
<h3 className="mb-4 text-lg font-semibold">Popular Posts</h3>
<ul className="space-y-3">
{posts.map((post, index) => (
<li key={post.slug} className="flex items-start gap-3">
<span className="flex h-6 w-6 items-center justify-center rounded-full bg-brand-100 text-sm font-semibold text-brand-800">
{index + 1}
</span>
<div className="flex-1">
<Link
href={`/blog/${post.slug}`}
className="font-medium text-gray-900 hover:text-brand-600"
>
{post.title}
</Link>
<p className="text-sm text-gray-600">{post.clicks.toLocaleString()} clicks</p>
</div>
</li>
))}
</ul>
</div>
);
}
// API route: pages/api/popular-posts.ts
import { NextApiRequest, NextApiResponse } from 039;next039;;
import shortLinks from 039;@/data/short-links.json039;;
const API_BASE = 039;https://api.sniplink.co/v1';
const API_KEY = process.env.SNIPLINK_API_KEY!;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
// Fetch analytics for all blog posts
const posts = await Promise.all(
Object.entries(shortLinks).map(async ([slug, shortUrl]) => {
const linkId = shortUrl.split(039;/039;).pop()!;
const response = await fetch(
`${API_BASE}/links/${linkId}/analytics?startDate=${
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
}`,
{
headers: { 039;X-API-Key039;: API_KEY },
}
);
const data = await response.json();
return {
slug,
clicks: data.clicks || 0,
};
})
);
// Sort by clicks and take top 5
const popular = posts
.sort((a, b) => b.clicks - a.clicks)
.slice(0, 5);
res.status(200).json({ posts: popular });
} catch (error) {
res.status(500).json({ error: 039;Failed to fetch popular posts039; });
}
}Best Practices
1. Use UTM Parameters
Automatically add UTM parameters to track campaign sources:
const linkData = {
url: postUrl,
slug: postSlug,
title: postTitle,
utmSource: 039;blog039;,
utmMedium: 039;social039;,
utmCampaign: 039;content-sharing039;,
};2. Cache Analytics Data
Don't fetch analytics on every page load:
// Regenerate popular posts daily using cron or build hooks
// Store results in a database or static JSON file
// Serve cached data to users3. Handle Rate Limits
If generating links for many posts, respect the 100 requests/15min limit:
// Add delay between requests
await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay4. Fallback URLs
Always provide a fallback if short link creation fails:
const shareUrl = shortLinks[slug] || `https://yourblog.com/blog/${slug}`;Measuring Success
Track these metrics to measure your blog's social performance:
- Click-through rate: Clicks divided by impressions
- Top referrers: Which social platforms drive the most traffic
- Peak sharing times: When your audience is most active
- Geographic distribution: Where your readers are located
- Device breakdown: Mobile vs desktop readership
Next Steps
- A/B test titles: Create multiple short links with different UTM parameters to test headlines
- Automate social posting: Use Make.com integration to auto-post new articles
- Email newsletters: Use short links in email campaigns to track click-through rates
- QR codes: Generate QR codes for print materials linking to blog posts
Need Help?
Having trouble integrating SnipLink with your blog? We're here to help.