Pagination & filtering
Page through Official API results — bounded pages with ListPage, lazy streaming with ListAll and Collect, and the filter DSL.
Official list endpoints are paged, and the SDK makes draining explicit so you never accidentally pull
thousands of rows. Every listable group exposes two methods: a bounded List…Page and a lazy
List…All. This guide uses Networks, but the shapes are identical across every group.
One bounded page: List…Page
List…Page(ctx, siteId, *official.ListOptions) fetches exactly one page and returns an
official.Page[T]. Pass nil options for the first page at the default size:
page, err := c.Official().Networks().ListPage(ctx, siteID, nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("got %d of %d (offset=%d limit=%d)\n", page.Count, page.TotalCount, page.Offset, page.Limit)
for _, n := range page.Items {
fmt.Println(n.Name)
}The returned official.Page[T] carries both the items and the envelope counters:
Prop
Type
Bounding and filtering a page
Pass an official.ListOptions to control offset, size and a server-side filter:
page, err := c.Official().Networks().ListPage(ctx, siteID, &official.ListOptions{
Offset: 0,
Limit: 50, // clamped to 200 if larger
Filter: "name.eq('lan')",
})Prop
Type
The Official spec caps page size at 200. A Limit that is non-positive or above 200 is clamped to 200
for you, so a page never silently returns more than you asked for.
Streaming every item: List…All
List…All(ctx, siteId, filter) returns an iter.Seq2[T, error] that pages on demand. Range over it
and break to stop — no further requests are made once you stop:
for net, err := range c.Official().Networks().ListAll(ctx, siteID, "") {
if err != nil {
log.Fatal(err)
}
fmt.Println(net.Name)
if net.Default {
break // no further pages are fetched
}
}The filter argument is the same expression as ListOptions.Filter; pass "" to stream unfiltered. Each
loop iteration yields either an item or an error (the gate failing, or a page fetch failing), so always
check err inside the loop.
Materializing with Collect
When you genuinely want every match in a slice, wrap the iterator with official.Collect. It drains the
stream and short-circuits on the first error:
all, err := official.Collect(c.Official().Networks().ListAll(ctx, siteID, "name.eq('lan')"))
if err != nil {
log.Fatal(err)
}
fmt.Println("matched", len(all))Collect (and an un-break-ed ListAll) loads all matching rows into memory — that's the explicit
opt-in. Prefer ranging with a break, or a single ListPage, when you don't need the whole set.
The filter DSL
Filter is a server-side expression evaluated by the controller. It uses a field.op('value') form, for
example name.eq('lan'). Filtering happens on the controller, so it narrows results before they are
paged and transferred. Refer to the UniFi API Reference for the operators and fields
each endpoint accepts.