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.