Logo

Blog

Retrieve and render blog content using the Quotient SDK

Overview

The blog API lets you retrieve published blog posts, authors, and tags from your Quotient blog. All endpoints are read-only and work with both public and private API keys.

EndpointAPI KeyServer SDKClient SDK
Get blog postpublic or privateblog.get()blog.get()
List blog postspublic or privateblog.list()blog.list()
List authorspublic or privateblog.listAuthors()blog.listAuthors()

Get a Blog Post

blog.get(options)

GET /api/v0/blog/{slug}

Auth: public or private key · scope: BLOG_READ

ParamTypeRequiredDescription
slugstringYesBlog post slug
rawHtmlbooleanNoReturn pre-rendered HTML
const { blog } = await client.blog.get({
  slug: "my-first-post",
  rawHtml: true,
});

Returns:

{
  blog: {
    id: string;
    title: string;
    slug: string;
    content: JSON;
    dominantImageUrl?: string | null;
    publishDate: Date | null;
    rawHtml?: string | null;
    authors: {
      id: string;
      name: string;
      emailAddress?: string | null;
      avatarUrl?: string | null;
    }[];
    metaDescription: string | null;
    tags: {
      id: string;
      name: string;
      description?: string | null;
    }[];
  };
}

List Blog Posts

blog.list(options?)

GET /api/v0/blog/list

Auth: public or private key · scope: BLOG_READ

ParamTypeRequiredDescription
authorIdsstring[]NoFilter by author IDs
tagIdsstring[]NoFilter by tag IDs
statuses("DRAFT" | "SCHEDULED" | "PUBLISHED")[]NoFilter by status
searchstringNoSearch by title
pagenumberNoPage number (default 1)
limitnumberNoResults per page (default 50)
const { blogs, pageData } = await client.blog.list({
  statuses: ["PUBLISHED"],
  search: "marketing",
  page: 1,
  limit: 20,
});

Returns:

{
  blogs: {
    id: string;
    title: string;
    slug: string;
    content: JSON;
    dominantImageUrl?: string | null;
    publishDate: Date | null;
    authors: {
      id: string;
      name: string;
      emailAddress?: string | null;
      avatarUrl?: string | null;
    }[];
    metaDescription: string | null;
    tags: {
      id: string;
      name: string;
      description?: string | null;
    }[];
  }[];
  pageData: {
    page: number;
    limit: number;
    total: number;
    isNextPageAvailable: boolean;
  };
}

List Authors

blog.listAuthors(options?)

GET /api/v0/blog/authors

Auth: public or private key · scope: BLOG_READ

ParamTypeRequiredDescription
searchstringNoFilter by name
pagenumberNoPage number (default 1)
limitnumberNoResults per page (default 10)
const { authors, pageData } = await client.blog.listAuthors({
  search: "jane",
  page: 1,
  limit: 10,
});

Returns:

{
  authors: {
    id: string;
    name: string;
    emailAddress?: string | null;
    avatarUrl?: string | null;
  }[];
  pageData: {
    page: number;
    limit: number;
    total: number;
    isNextPageAvailable: boolean;
  };
}

Rendering

The @quotientjs/react package provides a <Blog /> component that renders blog content into HTML. It takes the content JSON from blog.get() and produces unstyled semantic HTML elements.

Basic Usage

import { Blog } from "@quotientjs/react";
import { QuotientServer } from "@quotientjs/server";

// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const client = new QuotientServer({
    privateKey: process.env.QUOTIENT_PRIVATE_KEY!,
  });

  const { blog } = await client.blog.get({ slug: params.slug });

  return <Blog content={blog.content} />;
}

Styling

The <Blog /> component renders unstyled HTML with default class names on each element (quotient-p, quotient-h1, quotient-a, etc.). There are three ways to style blog content:

1. Target default classes in CSS

.quotient-p { line-height: 1.75; }
.quotient-h2 { margin-top: 2rem; }
.quotient-a { color: blue; text-decoration: underline; }

2. Override classes via elementClassName

<Blog
  content={blog.content}
  elementClassName={{
    p: "text-base leading-7",
    h2: "text-2xl font-bold mt-8",
    a: "text-blue-600 underline",
  }}
/>

3. Tailwind Typography plugin

<Blog content={blog.content} className="prose prose-lg" />

Available Class Targets

ElementDefault Class
<strong>quotient-strong
<em>quotient-em
<u>quotient-u
<a>quotient-a
<p>quotient-p
<h1><h6>quotient-h1quotient-h6
<ul>quotient-ul
<ol>quotient-ol
<li>quotient-li
<img>quotient-image
assetsquotient-asset

Client-Side Fetching

Within client components inside a <QuotientProvider>, use the useBlogs hook for client-side blog fetching:

// app/blog/blog-list.tsx
"use client";
import { BlogStatus, useBlogs } from "@quotientjs/react";

export default function BlogList({
  page,
  limit,
  statuses,
}: {
  page: number;
  limit: number;
  statuses: BlogStatus[];
}) {
  const { data, isLoading } = useBlogs({
    page,
    limit,
    statuses,
  });

  const { blogs, pageData } = data;
  return (
    {/* Render blog list according to your own specs */}
  );
}

// app/blog/page.tsx
"use client";
import { QuotientProvider, BlogStatus } from "@quotientjs/react";

const PAGE_SIZE = 50;

export default function Blogs() {
  const [page, setPage] = useState<number>(1);
  const [statuses, setStatuses] = useState<BlogStatus[]>([]);

  return (
    <QuotientProvider clientOptions={clientOptions}>
      <BlogList page={page} limit={PAGE_SIZE} statuses={statuses} />
    </QuotientProvider>
  );
}

You can also fetch blogs using the client directly via useQuotient:

// app/blog/blog-list.tsx
import { QuotientProvider, BlogStatus, useQuotient } from "@quotientjs/react";
import { useEffect, useState } from "react";

export default function BlogList({
  page,
  limit,
  statuses,
}: {
  page: number;
  limit: number;
  statuses: BlogStatus[];
}) {
  const { client } = useQuotient();
  const [blogs, setBlogs] = useState<any[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [hasNextPage, setHasNextPage] = useState(false);

  useEffect(() => {
    async function fetchBlogs() {
      try {
        setIsLoading(true);
        setError(null);

        const response = await client.blog.list({
          page,
          limit,
          statuses,
        });

        setBlogs(response.blogs);
        setHasNextPage(response.pageData.isNextPageAvailable);
      } catch (err) {
        setError(err instanceof Error ? err.message : "An error occurred");
      } finally {
        setIsLoading(false);
      }
    }

    fetchBlogs();
  }, [page, client]);

  return (
    {/* Render blog list according to your own specs */}
  );
}

// app/blog/page.tsx
"use client";
import { QuotientProvider } from "@quotientjs/react";

const PAGE_SIZE = 50;

export default function Blogs() {
  const [page, setPage] = useState<number>(1);
  const [statuses, setStatuses] = useState<BlogStatus[]>([]);

  return (
    <QuotientProvider clientOptions={clientOptions}>
      <BlogList page={page} limit={PAGE_SIZE} statuses={statuses} />
    </QuotientProvider>
  );
}

Next Steps