Skip to content
thefaqappthefaqapp
Browse docs

CRUD workflows

Common content-management workflows. Create, list, update, organize, and bulk-import questions and categories.

Updated 2026-10-11

The day-to-day patterns for managing FAQ content. Every example uses @faqapp/core; the equivalent cURL is in the API reference.

Setup

import { createFAQClient } from "@faqapp/core";

const faq = createFAQClient({
  apiKey: process.env.FAQAPP_API_KEY,
  organizationSlug: "acme"
});

The client wraps the v1 API with typed methods, automatic retries on transient errors, and the typed error classes from Errors. Writes need a server key with the write scope.

Create a question

const { data: question } = await faq.questions.create({
  question: "How do I cancel my subscription?",
  answer: "Open Settings, then Billing, then Cancel subscription. You keep access through the end of the billing period.",
  categorySlug: "billing",
  published: true
});

console.log(question.id, question.slug);

Pass categorySlug or categoryId. The slug derives from question unless you pass slug. Leave out published to save a draft.

List and paginate

const page = await faq.questions.list({ status: "published", limit: 50 });

console.log(page.data.length, page.pagination.total);
console.log(page.hasNextPage());

Lists are page-numbered. page.pagination holds page, limit, total, and pages. To walk every page, iterate:

import type { Question } from "@faqapp/core";

const all: Question[] = [];
for await (const item of faq.questions.list({ limit: 100 }).iter()) {
  all.push(item);
}

console.log(`fetched ${all.length} questions`);

Update a question

Questions are addressed by slug or id:

await faq.questions.update("how-do-i-cancel-my-subscription", {
  answer: "Updated answer. Each update creates a revision."
});

Updates are partial: only the fields you pass change. Every update writes a revision, and the 50 most recent are kept per question. See Revisions below.

Delete a question

const { data: deleted } = await faq.questions.delete("how-do-i-cancel-my-subscription");
console.log(deleted.id, deleted.deleted);

Delete unpublishes the question and hides it from every read. A slug that does not exist throws FAQNotFoundError.

Create and organize categories

await faq.categories.create({
  name: "Billing",
  description: "Subscriptions, invoices, and refunds.",
  order: 10
});

await faq.categories.create({ name: "API", order: 20 });
await faq.categories.create({ name: "Security", order: 30 });

order controls sort, and lower numbers come first. Use multiples of 10 so you can insert between them later without renumbering.

To move a question between categories:

await faq.questions.update("how-do-i-cancel-my-subscription", { categorySlug: "billing" });

Bulk import

For a first migration, send up to 100 questions per bulk call. Rows whose slug already exists are updated, so a re-run after a crash does not create duplicates:

const rows = [
  { slug: "cancel-subscription", question: "How do I cancel?", answer: "Open Settings, then Billing.", category: "billing" },
  { slug: "export-data", question: "Can I export my data?", answer: "Yes. Use the export endpoint.", category: "api" }
];

const { data: result } = await faq.questions.bulk({
  questions: rows.map(row => ({
    slug: row.slug,
    question: row.question,
    answer: row.answer,
    categorySlug: row.category
  }))
});

console.log(`${result.created} created, ${result.updated} updated`);

For CSV or JSON files with a dry-run preview, use faq.questions.import.

Revisions and history

Every update creates a revision. Read the history, then restore one by id:

const { data: revisions } = await faq.questions.revisions("how-do-i-cancel-my-subscription");
const oldest = revisions.at(-1);
if (oldest) {
  console.log(oldest.createdAt, oldest.question);
  await faq.questions.restoreRevision("how-do-i-cancel-my-subscription", oldest.id);
}

Storage rolls, so revision 51 evicts revision 1.

Translations

Once you have target languages configured (see Translations), translate a question with AI, edit the draft, then publish it:

for (const language of ["es", "de", "fr"]) {
  await faq.translations.aiTranslate("questions", "how-do-i-cancel-my-subscription", { language });
}

await faq.translations.upsert("questions", "how-do-i-cancel-my-subscription", "es", {
  answer: "Una respuesta más clara en español."
});

await faq.translations.upsert("questions", "how-do-i-cancel-my-subscription", "es", {
  publishedAt: new Date().toISOString()
});

AI translations save as drafts for your review. The source language stays authoritative: if a reader’s language is not published yet, they see the source.