go-unifi

Wireless

Manage WLANs, AP and WLAN groups, RADIUS profiles, channel plans, and the guest hotspot — including Official-API vouchers and broadcasts.

Wireless configuration spans several Internal-API resources — SSIDs (WLAN), the groups that scope them (WLANGroup, APGroup), authentication (RADIUSProfile), RF planning (ChannelPlan), and the guest hotspot (HotspotOp, HotspotPackage, Hotspot2Conf, BroadcastGroup). Guest vouchers and Wi-Fi broadcasts also have a modern surface on the Official API.

All Internal examples assume a constructed client c and a ctx — see the Quickstart.

Creating a WLAN (SSID)

A WLAN is one SSID. At minimum set a name, a security mode, and the network and user group it belongs to. For WPA2-Personal, set Security: "wpapsk", a WPAMode, and an XPassphrase.

func exampleWLAN(ctx context.Context, c unifi.Client, networkID, userGroupID string) error {
	wlan, err := c.CreateWLAN(ctx, "default", &unifi.WLAN{
		Name:        "Office",
		Enabled:     true,
		Security:    "wpapsk", // open | wpapsk | wep | wpaeap | osen
		WPAMode:     "wpa2",
		XPassphrase: "super-secret-pass",
		NetworkID:   networkID,
		UserGroupID: userGroupID,
		WLANBands:   []string{"2g", "5g"},
	})
	if err != nil {
		return fmt.Errorf("create wlan: %w", err)
	}

	wlans, err := c.ListWLAN(ctx, "default")
	if err != nil {
		return fmt.Errorf("list wlans: %w", err)
	}
	for _, w := range wlans {
		fmt.Printf("%s (enabled=%v)\n", w.Name, w.Enabled)
	}

	return c.DeleteWLAN(ctx, "default", wlan.ID)
}

CreateWLAN initialises an empty Schedule for you when you leave it unset. For WPA-Enterprise, use Security: "wpaeap" and point RADIUSProfileID at a profile (below). The full field set — band steering, fast roaming, PPSK, MAC filtering, Hotspot 2.0 — is on pkg.go.dev.

WLAN groups and AP groups

A WLANGroup bundles SSIDs together; an APGroup restricts broadcasting to specific access points by MAC. A WLAN references them via WLANGroupID and ApGroupIDs.

func exampleWLANGroupAPGroup(ctx context.Context, c unifi.Client) error {
	wg, err := c.CreateWLANGroup(ctx, "default", &unifi.WLANGroup{Name: "guest-aps"})
	if err != nil {
		return fmt.Errorf("create wlan group: %w", err)
	}
	_ = wg

	ag, err := c.CreateAPGroup(ctx, "default", &unifi.APGroup{
		Name:       "lobby",
		DeviceMACs: []string{"00:11:22:33:44:55"},
	})
	if err != nil {
		return fmt.Errorf("create ap group: %w", err)
	}
	_ = ag
	return nil
}

RADIUS profiles

A RADIUSProfile holds the authentication and accounting servers used by WPA-Enterprise SSIDs and wired 802.1X. Each server is a RADIUSProfileAuthServers / RADIUSProfileAcctServers entry with an IP, port, and shared secret.

func exampleRADIUSProfile(ctx context.Context, c unifi.Client) error {
	p, err := c.CreateRADIUSProfile(ctx, "default", &unifi.RADIUSProfile{
		Name:              "corp-radius",
		AccountingEnabled: true,
		AuthServers: []unifi.RADIUSProfileAuthServers{
			{IP: "10.0.0.10", Port: 1812, XSecret: "shared-secret"},
		},
		AcctServers: []unifi.RADIUSProfileAcctServers{
			{IP: "10.0.0.10", Port: 1813, XSecret: "shared-secret"},
		},
	})
	if err != nil {
		return fmt.Errorf("create radius profile: %w", err)
	}
	// Reference p.ID from a WLAN via WLAN.RADIUSProfileID for WPA-Enterprise.
	_ = p
	return nil
}

Channel plans

The controller's auto-RF feature produces ChannelPlan records — a snapshot of the channel and transmit-power assignment computed for each radio. They are read-mostly; list them to inspect a plan.

func exampleChannelPlan(ctx context.Context, c unifi.Client) error {
	plans, err := c.ListChannelPlan(ctx, "default")
	if err != nil {
		return fmt.Errorf("list channel plans: %w", err)
	}
	for _, plan := range plans {
		fmt.Printf("plan %s computed at %s with %d radios\n", plan.ID, plan.Date, len(plan.RadioTable))
	}
	return nil
}

Guest hotspot

The hotspot/guest portal is built from several resources: a HotspotOp operator account staff use to manage guests, HotspotPackage paid-access tiers, Hotspot2Conf (Passpoint / Hotspot 2.0) profiles, and BroadcastGroups. Each is a standard CRUD resource.

func exampleHotspot(ctx context.Context, c unifi.Client) error {
	// An operator account staff use to manage the guest portal.
	op, err := c.CreateHotspotOp(ctx, "default", &unifi.HotspotOp{
		Name:      "front-desk",
		XPassword: "operator-pass",
	})
	if err != nil {
		return fmt.Errorf("create hotspot operator: %w", err)
	}
	_ = op

	// A paid-access package offered on the portal.
	pkg, err := c.CreateHotspotPackage(ctx, "default", &unifi.HotspotPackage{
		Name:     "1-day",
		Amount:   5.0,
		Currency: "USD",
		Hours:    24,
	})
	if err != nil {
		return fmt.Errorf("create hotspot package: %w", err)
	}
	_ = pkg

	// Hotspot 2.0 (Passpoint) profiles and broadcast groups.
	confs, err := c.ListHotspot2Conf(ctx, "default")
	if err != nil {
		return fmt.Errorf("list hotspot 2.0 configs: %w", err)
	}
	_ = confs

	bg, err := c.CreateBroadcastGroup(ctx, "default", &unifi.BroadcastGroup{
		Name:        "all-aps",
		MemberTable: []string{},
	})
	if err != nil {
		return fmt.Errorf("create broadcast group: %w", err)
	}
	_ = bg
	return nil
}

Vouchers and broadcasts on the Official API

Guest vouchers and Wi-Fi broadcasts also have a first-class surface on the Official API, reached via c.Official(). Unlike the Internal API, the Official surface keys on a site UUID — resolve it from the site name with Sites().ResolveID.

func exampleVouchers(ctx context.Context, c unifi.Client) error {
	siteID, err := c.Official().Sites().ResolveID(ctx, "default")
	if err != nil {
		return fmt.Errorf("resolve site: %w", err)
	}

	count := int32(10)
	result, err := c.Official().Hotspot().CreateVouchers(ctx, siteID, official.HotspotVoucherCreationRequest{
		Name:             "lobby-guests",
		Count:            &count,
		TimeLimitMinutes: 1440, // 24 hours of access per voucher
	})
	if err != nil {
		return fmt.Errorf("create vouchers: %w", err)
	}
	if result.Vouchers != nil {
		for _, v := range *result.Vouchers {
			fmt.Printf("voucher %s code %s\n", v.Id, v.Code)
		}
	}

	// Drain every existing voucher, paging on demand.
	for v, err := range c.Official().Hotspot().ListVouchersAll(ctx, siteID, "") {
		if err != nil {
			return fmt.Errorf("list vouchers: %w", err)
		}
		fmt.Println(v.Code)
	}
	return nil
}

ListVouchersAll returns a Go 1.23 range-over-func iterator (iter.Seq2) that pages on demand — break out of the loop to stop early without fetching more pages. The filter argument accepts an Official-API filter expression, or "" for everything. See Pagination and filtering for the paging helpers (ListVouchersPage, official.Collect).

Wi-Fi broadcasts work the same way — ListAll for an iterator, Get for a single record by its UUID:

func exampleWifiBroadcasts(ctx context.Context, c unifi.Client, siteID uuid.UUID) error {
	for b, err := range c.Official().WifiBroadcasts().ListAll(ctx, siteID, "") {
		if err != nil {
			return fmt.Errorf("list wifi broadcasts: %w", err)
		}
		details, err := c.Official().WifiBroadcasts().Get(ctx, siteID, b.Id)
		if err != nil {
			return fmt.Errorf("get wifi broadcast: %w", err)
		}
		fmt.Printf("%s enabled=%v\n", details.Name, details.Enabled)
	}
	return nil
}

The Official surface requires controller 10.1.78 or newer and an API key. The request and response models (HotspotVoucherCreationRequest, WifiBroadcastCreateOrUpdate, …) live in the official package; the raw endpoints are in the UniFi API reference.

Next steps

On this page