How I Built an AI Tool That Writes Product Manuals as PDFs
I’ve been working on an AI search product lately, and one thing I needed was proper product manuals. At first, I was creating all the PDFs manually — which got repetitive fast. So, to save time (and honestly out of curiosity), I built a small program that generates manuals automatically using AI.
For this project, I decided to use my new go-to language — Go (Golang).
You might wonder why I didn’t use my main language, PHP. Well, I just wanted to try something new and have some fun experimenting.
To handle the AI part, I used the package github.com/openai/openai-go/v3
for making requests to OpenAI’s API.
For PDF generation, I went with github.com/signintech/gopdf
, which is great for creating simple PDFs (nothing fancy in design — just clean and functional).
And finally, I used github.com/joho/godotenv
to load environment variables — because yeah, I’d rather not leak my API keys while testing with ChatGPT. 😅
Since I don’t have much time to manually create a PDF manual for every new product I add, I started thinking of ways to automate the process. I’ve seen a lot of people use AI to speed up their work — and in my case, it’s just a side project — so an idea popped into my head:
Why not build a program that generates the manuals for me?
I brainstormed a bunch of approaches but decided to stick with something simple — you know, KISS (Keep It Simple, Stupid).
The idea was straightforward: take a product name and build a prompt using some predefined, general-purpose questions.
Of course, before jumping into code, I had to figure out how real companies actually write their product manuals. So I did a bit of research (with some help from GPT, of course).
Once I had the structure — product name + predefined questions — it was time to start cooking. (I’ve always wanted to say that!) 🍳
func GetProductName() string {
fmt.Print("Enter product name: ")
defer fmt.Println("Name was received")
var productName string
_, err := fmt.Scan(&productName)
if err != nil {
log.Fatal(err)
}
return productName
}then , I created a Question struct to hold all the predefined questions:
type Question struct {
ID int
Question string
Type string
}And a function to return all the questions:
func GetQuestions() []Question {
return []Question{
{ID: 1, Question: "What product is for", Type: "text"},
{ID: 2, Question: "What feature this product has", Type: "list"},
{ID: 3, Question: "How to setup this product", Type: "text"},
{ID: 4, Question: "How to maintaine this product", Type: "text"},
{ID: 5, Question: "How to use this product", Type: "text"},
{ID: 6, Question: "What are the technical specifications", Type: "list"},
{ID: 7, Question: "What materials is the product made from", Type: "text"},
{ID: 8, Question: "What safety precautions should be followed", Type: "list"},
{ID: 9, Question: "What warranty does the product have", Type: "text"},
{ID: 10, Question: "What are common issues and troubleshooting steps", Type: "list"},
{ID: 11, Question: "What accessories or spare parts are available", Type: "list"},
{ID: 12, Question: "How to store the product when not in use", Type: "text"},
{ID: 13, Question: "How long is the expected lifespan of the product", Type: "text"},
{ID: 14, Question: "Is the product eco-friendly or recyclable", Type: "text"},
{ID: 15, Question: "Who to contact for support or repairs", Type: "text"},
}
}Next, I built a function to generate a prompt that I can send to OpenAI:
func gptPromptBuilder(productName string) string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("Generate answers for the following questions about %s.\n", productName))
builder.WriteString("Return your response **only** as a valid JSON object, no markdown, no explanations.\n")
builder.WriteString("Each key must be the exact question, and each value the detailed answer.\n\n")
builder.WriteString("Questions:\n")
for _, q := range GetQuestions() {
builder.WriteString(fmt.Sprintf("- %s?\n", q.Question))
}
return builder.String()
}Then , I wrote a function to call the OpenAI API using my prompt:
func OpenAiClient(prompt string) (*responses.Response, error) {
client := openai.NewClient(option.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
params := responses.ResponseNewParams{
Model: openai.ChatModelGPT4oMini,
Temperature: openai.Float(0.7),
MaxOutputTokens: openai.Int(2000),
Input: responses.ResponseNewParamsInputUnion{
OfString: openai.String(prompt),
},
}
resp, err := client.Responses.New(context.TODO(), params)
if err != nil {
log.Fatalf("Error calling OpenAI API: %v", err)
}
return resp, err
}next i will use the return response func OpenAiClient to make a map with question as key and answers as value
so i can map over them and printing question and answer like in the following code:
myQuestionsAndAnswers := make(map[string]string)
if err := json.Unmarshal([]byte(strings.TrimSpace(response.OutputText())), &myQuestionsAndAnswers); err != nil {
log.Fatalf("❌ Failed to parse GPT JSON: %v\nResponse was:\n%s", err, strings.TrimSpace(response.OutputText()))
}I used gopdf to generate the PDF. First, initialize the instance and set the font:
pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
fontPath := os.Getenv("FONT_PATH")
if _, err := os.Stat(fontPath); os.IsNotExist(err) {
log.Fatalf("Font path does not exist: %s", fontPath)
}
pdf.AddTTFFont("ARIAL", fontPath)
pdf.SetFont("ARIAL", "", 12)
I set some basic parameters for layout:
pageMargin := 40.0 lineHeight := 16.0 pageHeight := gopdf.PageSizeA4.H maxWidth := gopdf.PageSizeA4.W - 2*pageMargin
Then iterate over the questions and answers, creating pages as needed:
pdf.AddPage()
y := pageMargin
for _, q := range questions {
answer := myQuestionsAndAnswers[q.Question]
pdf.SetXY(pageMargin, y)
pdf.SetFont("ARIAL", "", 14)
pdf.Text(fmt.Sprintf("%s:", q.Question))
y += lineHeight * 1.5
pdf.SetFont("ARIAL", "", 12)
lines := helpers.SplitTextToLines(&pdf, answer, maxWidth)
for _, line := range lines {
if y+lineHeight > pageHeight-pageMargin {
pdf.AddPage()
y = pageMargin
}
pdf.SetXY(pageMargin, y)
pdf.Text(line)
y += lineHeight
}
y += lineHeight
}Finally, save the PDF:
pdfName := fmt.Sprintf("%s_manual.pdf", productName)
pdf.WritePdf(pdfName)now this was for me my first experienc making ai do something for me like .
The next step is to make this code available through my API — so that a PDF manual can be generated automatically for any new product created. I’ll cover that in another article.
