Documentation

GIF search in any JavaScript app

A framework-independent TypeScript client for Vue, Svelte, vanilla JavaScript and Node.js. Bring your own interface.

1. Install the client

Terminal
npm install @rowix/gifsnap-js

Install the JavaScript client from npm. TypeScript declarations are included.

The package has no runtime dependencies or React requirement. Use it with an ESM-capable bundler, modern browser, or Node.js 18 and later.

2. Search for a GIF

JavaScript / TypeScript
import { createGifSnapClient } from '@rowix/gifsnap-js';

const gifsnap = createGifSnapClient();
const { data, pagination } = await gifsnap.search({
  query: 'hello', limit: 12,
});

console.log(data[0]?.url);

Render gif.url to show animation. The preview_url can be a still image. Keep complete URLs, including query parameters, and use dimensions to reserve layout space.

3. Browse stickers or trending results

GIFs & stickers
const trending = await gifsnap.trending({ limit: 12 });
const stickers = await gifsnap.searchStickers({
  query: 'hello', limit: 12,
});
const trendingStickers = await gifsnap.trendingStickers();

Cancel stale searches

Create an abort controller for the request and cancel it when the component unmounts or the search changes. Handle a failed request separately from an empty result.

Cancellation & errors
import { createGifSnapClient, GifSnapError } from '@rowix/gifsnap-js';

const client = createGifSnapClient({ timeoutMs: 15000 });
const controller = new AbortController();

try {
  const result = await client.search({
    query: 'celebrate', signal: controller.signal,
  });
  console.log(result.data);
} catch (error) {
  if (!controller.signal.aborted) {
    console.error(error instanceof GifSnapError ? error.code : error);
  }
}

// In your component cleanup: controller.abort();

Load another page

Explicit pagination
const query = 'hello';
const limit = 12;
const first = await client.search({ query, limit });
if (first.pagination.has_next && first.pagination.next_page !== null) {
  const next = await client.search({
    query, limit, page: first.pagination.next_page,
  });
  console.log(next.data);
}

Keep the query and limit unchanged. The client preserves raw API results and counts. In your UI, use an optional verified content_id, an exact item ID, or an exact URL to suppress repeated media while preserving first-seen order. Do not infer identity from titles or stripped filenames.

Vue and Svelte examples

These complete components use the same JavaScript client. They cancel superseded requests and return the selected media. Style the markup to fit your application.

Vue 3: GifSearch.vue
GifSearch.vue
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { createGifSnapClient, type GifSnapGif } from '@rowix/gifsnap-js';
const emit = defineEmits<{ select: [gif: GifSnapGif] }>();
const client = createGifSnapClient();
const query = ref('hello'), items = ref<GifSnapGif[]>([]);
const loading = ref(false), error = ref('');
let controller: AbortController | undefined;
async function search() {
  controller?.abort();
  const request = new AbortController(); controller = request;
  loading.value = true; error.value = ''; items.value = [];
  try {
    const options = { limit: 12, signal: request.signal };
    const result = query.value.trim()
      ? await client.search({ ...options, query: query.value })
      : await client.trending(options);
    if (!request.signal.aborted) items.value = result.data;
  } catch {
    if (!request.signal.aborted) error.value = 'Could not load GIFs. Try again.';
  } finally {
    if (!request.signal.aborted) loading.value = false;
  }
}
onMounted(() => { void search(); });
onUnmounted(() => controller?.abort());
</script>

<template>
  <form @submit.prevent="search">
    <label>Search GIFs <input v-model="query" type="search" maxlength="120" /></label>
    <button type="submit">Search</button>
  </form>
  <p role="status">{{ loading ? 'Loading GIFs…' : `${items.length} GIFs` }}</p>
  <p v-if="error" role="alert">{{ error }}</p>
  <ul>
    <li v-for="(gif, index) in items" :key="`${gif.id}:${index}`">
      <button type="button" :aria-label="`Select ${gif.title || 'GIF'}`" @click="emit('select', gif)">
        <img :src="gif.url" alt="" :width="gif.width || 240" :height="gif.height || 180" loading="lazy" />
        {{ gif.title }}
      </button>
    </li>
  </ul>
  <a href="https://gifsnap.com" target="_blank" rel="noopener noreferrer">Powered by GifSnap</a>
</template>

Listen for selection with <GifSearch @select="handleSelect" />.

Svelte 5: GifSearch.svelte
GifSearch.svelte
<script lang="ts">
  import { onMount } from 'svelte';
  import { createGifSnapClient, type GifSnapGif } from '@rowix/gifsnap-js';
  let { onSelect }: { onSelect: (gif: GifSnapGif) => void } = $props();
  const client = createGifSnapClient();
  let query = $state('hello'), items = $state<GifSnapGif[]>([]);
  let loading = $state(false), error = $state('');
  let controller: AbortController | undefined;
  async function search(event?: SubmitEvent) {
    event?.preventDefault();
    controller?.abort();
    const request = new AbortController(); controller = request;
    loading = true; error = ''; items = [];
    try {
      const options = { limit: 12, signal: request.signal };
      const result = query.trim()
        ? await client.search({ ...options, query })
        : await client.trending(options);
      if (!request.signal.aborted) items = result.data;
    } catch {
      if (!request.signal.aborted) error = 'Could not load GIFs. Try again.';
    } finally {
      if (!request.signal.aborted) loading = false;
    }
  }
  onMount(() => {
    void search();
    return () => controller?.abort();
  });
</script>

<form onsubmit={search}>
  <label>Search GIFs <input bind:value={query} type="search" maxlength="120" /></label>
  <button type="submit">Search</button>
</form>
<p role="status">{loading ? 'Loading GIFs…' : `${items.length} GIFs`}</p>
{#if error}<p role="alert">{error}</p>{/if}
<ul>
  {#each items as gif, index (`${gif.id}:${index}`)}
    <li>
      <button type="button" aria-label={`Select ${gif.title || 'GIF'}`} onclick={() => onSelect(gif)}>
        <img src={gif.url} alt="" width={gif.width || 240} height={gif.height || 180} loading="lazy" />
        {gif.title}
      </button>
    </li>
  {/each}
</ul>
<a href="https://gifsnap.com" target="_blank" rel="noopener noreferrer">Powered by GifSnap</a>

Pass your callback with <GifSearch onSelect={handleSelect} />.

Use another language

The same REST API works from Flutter, Python, Go and other HTTP clients. For ready-made interfaces, use the React, Angular, SwiftUI or Compose picker.

No API key is required today. The SDK does not collect analytics, send cookies or retry automatically. Its MIT license covers the code; returned media retains its applicable rights and attribution.

SDK source and complete client reference ↗ · Response fields and errors