go-unifi

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:

firstpage.go
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:

bounded.go
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:

listall.go
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:

collect.go
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.

Next steps

On this page