Library / SDK
huandu/facebook avatar
huandu/facebook

huandu/facebook: a Facebook Graph API SDK for Go

A Facebook Graph API SDK For Go.

1,490 stars580 forksGoMIT

At a glance

What is it?
huandu/facebook is a Go package that wraps the Facebook Graph API, with file upload, batch requests and Marketing API support, and it decodes responses into Go structs or maps through a Result type. It suits a Go service that has to talk to Facebook, and its paging helper and error types are the parts worth understanding before you depend on it.
Who is it for?
huandu/facebook suits a Go service that calls the Facebook Graph API, especially one that needs batch requests or file upload without hand rolling them, and one that wants snake case response keys decoded into camel case Go fields. It does not suit a team that wants a generated, always current client, because the SDK is hand written and tracks Facebook's API by hand, and it does not suit a project that needs the newest Graph API features the day they appear.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 63 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A hand written wrapper with its own result type

huandu/facebook is a Go package that supports the Facebook Graph API with file upload, batch requests and the Marketing API, and the documentation says it can be used on Google App Engine. The module path carries a major version, so the import is github.com/huandu/facebook/v2, and the README explains why that matters. Since go1.14, incompatible versions are omitted unless specified explicitly, so the v2 path is the recommended import to avoid dependency errors, and the older non-versioned path is available with go get -u for projects that do not use go modules. The design decision that shapes everything is the Result type. A response is a map[string]interface{}, which the documentation calls fb.Result, and the package gives it methods for decoding into any Go type safely.

go
res, _ := fb.Get("/538744468", fb.Params{
    "fields": "first_name",
    "access_token": "a-valid-access-token",
})
fmt.Println("Here is my Facebook first name:", res["first_name"])

The decode helpers are DecodeField for a single key and Decode for a whole struct, and there is a detail worth knowing about them. If a target type implements json.Unmarshaler, the decoder will use it, so a *time.Time field is populated by the standard library's own time parsing rather than by anything Facebook specific.

go
var tm time.Time
res.DecodeField("create_time", &tm)

Two error types that tell you what actually went wrong

The error handling is the part of the package that shows real world use, because a Facebook API failure and a transport failure are different problems. The returned error can be a Facebook API error, in which case it is an *Error and carries the message, the type, the code, the error subcode and the trace ID, and it can be an unmarshal error when the API returns a message that is not JSON at all, in which case it is an *UnmarshalError carrying the message, the underlying error and the raw payload.

go
res, err := fb.Get("/me/feed", fb.Params{
     "access_token": "a-valid-access-token",
})

if err != nil {
    if e, ok := err.(*Error); ok {
        fmt.Printf("facebook error. [message:%v] [type:%v] [code:%v] [subcode:%v] [trace:%v]",
            e.Message, e.Type, e.Code, e.ErrorSubcode, e.TraceID)
        return
    }

    if e, ok := err.(*UnmarshalError); ok {
        fmt.Printf("facebook error. [message:%v] [err:%v] [payload:%v]",
            e.Message, e.Err, string(e.Payload))
        return
    }

    return
}

The trace ID deserves a note, because it is the identifier you give Facebook support when you have to ask them why a call failed. The subcode is the finer grained error code underneath the broad type. Separating the two error types also lets you retry intelligently, since an API error is usually permanent for that request while an unmarshal error on a non JSON body might be a transient gateway response. The package source layout follows this separation, with error.go holding the error types and api.go, session.go, result.go, params.go, paging_result.go, batch_result.go, conversion.go, filedata.go and app.go as the other units, most of them with a matching test file.

App and Session give you a place to put the credentials

The package offers two levels of entry. The package level functions, fb.Get and its siblings, are convenient but global. For a production application the documentation recommends App and Session, because they give more control over all API calls and make the code clearer and more concise. An App holds the app id and secret, and it also holds the redirect URI, which Facebook requires when parsing a signed request.

go
var globalApp = fb.New("your-app-id", "your-app-secret")

globalApp.RedirectUri = "http://your.site/canvas/url/"

session, _ := globalApp.SessionFromSignedRequest(signedRequest)

session := globalApp.Session(token)

err := session.Validate()

res, _ := session.Get("/me/feed", nil)

The two ways to obtain a session cover the common cases. SessionFromSignedRequest takes a Facebook signed request string from a query string and returns a session built from it, and Session takes a decoded access token directly. Validate then checks the access token by confirming the current user ID comes back properly, and it returns nil if the token is valid, which makes it a cheap pre-flight check. The App also carries the base URL override, and the documentation points out that by default all requests go to Facebook servers while the Session field can be set for unit testing.

go
testSrv := httptest.NewServer(someMux)
session.BaseURL = testSrv.URL + "/"

That override is what makes the package testable without network access, and the presence of session_test.go and facebook_test.go in the repository shows the package is exercised that way.

RFC3339 timestamps are opt in because Facebook does not use them

One of the small but genuinely useful behaviours is the timestamp handling. Facebook returns most timestamps in an ISO8601 format that Go's encoding/json cannot parse natively, and the package solves this by asking Facebook for a different format. Setting RFC3339Timestamps to true, either on the Session or at the global level, causes date_format=Y-m-d\TH:i:sP to be sent as a parameter on every request.

go
fb.RFC3339Timestamps = true
session.RFC3339Timestamps = true

The format string is a PHP date() representation of RFC3339, and the README links an issue with more background on the request. This matters more than it looks. Without it, decoding a response containing a timestamp into a time.Time field fails, and the failure surfaces as an unmarshal error rather than as a clear message about the date format. The conversion.go file in the repository is where the snake case to camel case mapping lives, which is the other conversion problem the package solves. Facebook always uses snake case keys in API responses, and the package maps those onto Go's camel case field names automatically, so a field named FooBar maps to foo_bar in JSON without any struct tags.

Paging walks the whole result set for you

Graph API responses that contain collections carry a special paging structure, and the package gives it a Result.Paging method so you can walk the data without hand rolling the next page URL.

go
res, _ := session.Get("/me/home", nil)

paging, _ := res.Paging(session)

var allResults []Result

allResults = append(allResults, paging.Data()...)

for {
  noMore, err := paging.Next()
  if err != nil {
    panic(err)
  }
  if noMore {
    break
  }
  allResults = append(allResults, paging.Data()...)
}

The shape of the loop is worth reading twice. Next returns a noMore flag alongside the error, so the loop ends when the server has no further pages, and Data returns the current page as a slice of Result. The paging structure is kept separate from the data, which is why a single page fetch and a full walk are both easy. This is where the value of a paging helper over raw HTTP shows, because the cursor and previous and next URL handling is exactly the part that is easy to get subtly wrong. The repository also carries batch_result.go, so the batch request path has the same treatment, and the package description lists batch requests and file upload as supported features alongside the core API.

Where huandu/facebook is the wrong tool

Three cases stand out. The first is a team that needs a client that is always current. This SDK is hand written, so a new Graph API feature appears only when somebody writes it, and the pace of releases reflects that, with v2.8.0 in October 2024, v2.9.0 in March 2025 and v2.9.2 in August 2026. A generated client from the official API description would track the surface more closely. The second is a team that wants typed request models. The response side is convenient because Result decodes into your structs, but the request side takes Params, which is a map, so a call like fb.Get takes strings and values rather than a typed request struct, and a mistake in a field name is a runtime error rather than a compile error. The third is a project with a strict toolchain policy. The go.mod declares go 1.13, which is a very old floor, and the module path carries a v2 major version, so a codebase that pins module paths has to accept that shape. On licensing, the package is MIT, which is permissive but still requires keeping the copyright notice, and the module is pure Go with no cgo requirement, so it is straightforward to vendor or fork if a Facebook API change needs a local fix.

Editorial conclusion

huandu/facebook suits a Go service that calls the Facebook Graph API, especially one that needs batch requests or file upload without hand rolling them, and one that wants snake case response keys decoded into camel case Go fields. It does not suit a team that wants a generated, always current client, because the SDK is hand written and tracks Facebook's API by hand, and it does not suit a project that needs the newest Graph API features the day they appear. Before you adopt it, pin a version and read the CHANGELOG.md, because the newest release is v2.9.2 from 2026-08-21 and the last push was on the same day, so the module is moving, and check that the go.mod entry, which declares go 1.13, still matches your toolchain policy.

Frequently asked questions

How do I install the huandu/facebook Go package?

With go modules enabled, run go get github.com/huandu/facebook/v2. The README recommends the v2 import path because since go1.14 incompatible versions are omitted unless specified explicitly, and the older path is only for projects without go modules.

How do I decode a Facebook Graph API response into a Go struct?

The response is a Result, a map[string]interface{}, and it has DecodeField for a single key and Decode for a whole struct. Snake case JSON keys are mapped onto camel case Go field names automatically, and a field whose type implements json.Unmarshaler is decoded by that implementation.

Why do Facebook timestamps fail to decode into time.Time?

Facebook returns timestamps in an ISO8601 format that Go's encoding/json cannot parse. Setting RFC3339Timestamps to true on the Session or globally makes the package request date_format=Y-m-d\TH:i:sP on every request, which is the RFC3339 format the standard library expects.

How do I read all pages of a Graph API collection?

Call res.Paging(session) to get the paging structure, then loop on paging.Next(), appending paging.Data() to your slice each time and stopping when the returned noMore flag is true.

Official sources

  1. huandu/facebook on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/huandu-facebook.svg)](https://hysenlabs.com/projects/huandu-facebook)

Was this useful?