Developing Product Q&A Feature for E-commerce
Q&A block reduces support inquiries and increases conversion: buyer gets answer right on product page. Questions accumulated over time form unique user-generated content, valuable for SEO. Takes 2–3 business days.
Data Schema
CREATE TABLE product_questions (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
user_id BIGINT REFERENCES users(id),
guest_name VARCHAR(100),
guest_email VARCHAR(255),
question TEXT NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
answer_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE product_answers (
id BIGSERIAL PRIMARY KEY,
question_id BIGINT REFERENCES product_questions(id) ON DELETE CASCADE,
user_id BIGINT REFERENCES users(id),
is_store_reply BOOLEAN DEFAULT FALSE,
body TEXT NOT NULL,
helpful_count INT DEFAULT 0,
status VARCHAR(20) DEFAULT 'approved',
created_at TIMESTAMP DEFAULT NOW()
);
Question Publishing
Guests can ask too — need email for answer notification:
public function store(Request $request, Product $product): JsonResponse {
$request->validate([
'question' => 'required|string|min:10|max:500',
'guest_name' => Rule::requiredIf(!$request->user()),
'guest_email'=> [Rule::requiredIf(!$request->user()), 'email'],
]);
$question = $product->questions()->create([
'question' => $request->question,
'user_id' => $request->user()?->id,
'guest_name' => $request->guest_name,
'guest_email' => $request->guest_email,
'status' => 'pending',
]);
Notification::route('mail', config('shop.support_email'))
->notify(new NewProductQuestion($question));
return response()->json([
'message' => 'Your question sent. We\'ll answer within 24 hours.',
], 201);
}
Store Answer
Manager answers from admin. Answer marked official, displays first:
public function answer(Request $request, ProductQuestion $question): JsonResponse {
$request->validate(['body' => 'required|string|min:5|max:1000']);
$answer = $question->answers()->create([
'user_id' => $request->user()->id,
'body' => $request->body,
'is_store_reply' => true,
'status' => 'approved',
]);
$question->update([
'status' => 'published',
'answer_count' => $question->answer_count + 1,
]);
$notifiable = $question->user ?? Notification::route('mail', $question->guest_email);
$notifiable->notify(new QuestionAnswered($question, $answer));
return response()->json(new AnswerResource($answer), 201);
}
Peer Answers
Other buyers can answer too. Their answers auto-moderated, sorted after store reply.
Page Display
const ProductQA = ({ productId }: { productId: number }) => {
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
queryKey: ['product-questions', productId],
queryFn: ({ pageParam = 1 }) =>
api.get(`/products/${productId}/questions`, { params: { page: pageParam, per_page: 5 } }),
getNextPageParam: (last) => last.meta.next_page,
});
const questions = data?.pages.flatMap(p => p.data) ?? [];
return (
<section>
<h2 className="text-xl font-semibold mb-4">Questions & Answers</h2>
<AskQuestionForm productId={productId} />
<div className="mt-6 space-y-4">
{questions.map(q => (
<QuestionCard key={q.id} question={q} />
))}
</div>
</section>
);
};
Question Search
If many questions, add search:
$questions = $product->questions()
->published()
->when($request->q, fn($query, $search) =>
$query->where('question', 'ilike', "%{$search}%")
->orWhereHas('answers', fn($q2) =>
$q2->where('body', 'ilike', "%{$search}%")
)
)
->paginate(10);
Answer Helpfulness
"Helpful" / "Not helpful" buttons under each answer. Sort answers by helpful_count — most valuable first.
SEO Value
Q&A indexed as part of product page. For additional SEO — FAQPage microdata:
<script type="application/ld+json">{JSON.stringify({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: questions.map(q => ({
'@type': 'Question',
name: q.question,
acceptedAnswer: q.answers[0]
? { '@type': 'Answer', text: q.answers[0].body }
: undefined,
})).filter(q => q.acceptedAnswer),
})}</script>
Chance for Google FAQ rich snippet — no extra cost.
Anti-Spam
IP rate limit (3 questions/hour), honeypot field, stopwords check. High-load stores — reCAPTCHA v3.







