← Go Back

How I Built an AI Tool That Writes Product Manuals as PDFs

Oct 08, 2025 6min
#golang #ai #pdf

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!) 🍳

064bbb93053de2823fd0b1a7d827dfbb.jpg 104.95 KB
let's start by getting product name using this function: 

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. 

Thank you!

789f3be7d72ca06a5e0506792e13d570.jpg 104.73 KB