Stickybeak Docs Dashboard Get early access

Backends

Go

Go analytics with Stickybeak starts by adding its script to your rendered pages; this guide shows you where it belongs and how to track page views, goals and purchase revenue.

Add the script

  1. Set the site ID

    Set STICKYBEAK_SITE_ID in your production environment only. Copy the ID from Settings in the dashboard.

  2. Pass it to every page

    Wrap your page data in a struct that carries the site ID, and render through one helper:

    var stickybeakSite = os.Getenv("STICKYBEAK_SITE_ID") // empty outside production
    
    type Goal struct {
    	Name  string
    	Props map[string]any
    }
    
    type Page struct {
    	StickybeakSite string
    	Goal           *Goal
    	Data           any
    }
    
    func render(w http.ResponseWriter, r *http.Request, name string, data any) {
    	page := Page{StickybeakSite: stickybeakSite, Goal: takeGoal(w, r), Data: data}
    	if err := tmpl.ExecuteTemplate(w, name, page); err != nil {
    		log.Print(err)
    	}
    }
  3. Add it to your head template

    {{define "head"}}
    <script>window.stickybeak = window.stickybeak || function () { (window.stickybeak.q = window.stickybeak.q || []).push(arguments); };</script>
    {{if .StickybeakSite}}
    <script
      defer
      data-site="{{.StickybeakSite}}"
      data-domain="example.com"
      src="https://app.usestickybeak.com/_cfw/s.js"
    ></script>
    {{end}}
    {{with .Goal}}
    <script>window.stickybeak({{.Name}}, {{.Props}});</script>
    {{end}}
    {{end}}

Check it's working

Publish first, then follow Verify your install.

Track goals

Give any button or link a data-goal attribute. See Custom goals.

<button data-goal="start_trial" data-goal-plan="pro">Start trial</button>

Fire a goal after a redirect

The handler that saves a signup or confirms a payment usually redirects.

const goalCookie = "sb_goal"

func setGoal(w http.ResponseWriter, g Goal) {
	b, _ := json.Marshal(g)
	http.SetCookie(w, &http.Cookie{
		Name: goalCookie, Value: base64.RawURLEncoding.EncodeToString(b),
		Path: "/", MaxAge: 60, HttpOnly: true, Secure: true, SameSite: http.SameSiteLaxMode,
	})
}

func takeGoal(w http.ResponseWriter, r *http.Request) *Goal {
	c, err := r.Cookie(goalCookie)
	if err != nil {
		return nil
	}
	http.SetCookie(w, &http.Cookie{Name: goalCookie, Path: "/", MaxAge: -1})
	b, err := base64.RawURLEncoding.DecodeString(c.Value)
	if err != nil {
		return nil
	}
	var g Goal
	if json.Unmarshal(b, &g) != nil {
		return nil
	}
	return &g
}
mux.HandleFunc("POST /signup", func(w http.ResponseWriter, r *http.Request) {
	user, err := createUser(r)
	if err != nil {
		// ...
		return
	}
	setGoal(w, Goal{Name: "signup", Props: map[string]any{"plan": user.Plan}})
	http.Redirect(w, r, "/welcome", http.StatusSeeOther)
})

Inside a <script>, html/template writes {{.Name}} as a quoted JavaScript string and {{.Props}} as a JSON object, escaped so nothing can break out of the tag.

If you already use a session package such as gorilla/sessions or scs, use its flash or pop-on-read helpers instead of the cookie.

Track purchases

setGoal(w, Goal{Name: "purchase", Props: map[string]any{
	"revenue": order.Total, "currency": order.Currency, "order_id": order.ID,
}})
Using templ or htmx?

With templ, render the same tags in your layout component.

See Revenue.

If it isn't working

  • If data-site is empty, the environment variable is not reaching the process.

See Troubleshooting.