-Add an unique feed name (no spaces, no special chars) and make the required changes: adapt at least the URL, check/change the rule, the size and the description for a new feed.
-The rule consist of max. 4 individual, space separated parameters:
-1. type: `feed` or `suricata` (required)
-2. prefix: an optional search term (a string literal, no regex) to identify valid IP list entries
-3. column: the IP column within the feed file, e.g. `1` (required)
-4. separator: an optional field separator, default is the character class `[[:space:]]`
-
-Please note: the flag field is optional, it's a space separated list of options: supported are `gz` as an archive format and protocols `tcp` or `udp` with port numbers/port ranges for destination port limitations.
+The object supports the following fields:
+
+| Field | Required | Description |
+| :------ | :------: | :--------------------------------------------------------------------------------------------------------------------- |
+| url_4 | yes\* | download URL of the IPv4 list. \*at least one of `url_4`/`url_6` must be present |
+| url_6 | yes\* | download URL of the IPv6 list. May point to the **same** URL as `url_4` if the source mixes IPv4 and IPv6 in one file |
+| rule | yes | the parsing ruleset, max. 4 space separated parameters (see below) |
+| chain | yes | the default chain/direction: `in`, `out` or `inout` (see below) |
+| descr | yes | a short human-readable description shown in LuCI and the feed table |
+| flag | no | optional, space separated list of extra options: archive format and/or protocol/port limitations (see below) |
+
+**The `url_4` / `url_6` fields**
+Each address family is fetched and processed independently. Three cases:
+* IPv4-only source: set `url_4` only, omit `url_6`
+* separate IPv4 and IPv6 files: set both to their respective URLs (e.g. `doh`, `spamhaus`)
+* a single dual-stack file that mixes v4 and v6 entries: point both `url_4` and `url_6` at that same URL. banIP fetches it only once and process it for each family and the per-family regex extracts the matching addresses; the non-matching lines are simply ignored. (e.g. `threatview`, which ships v4 and v6 in one file).
+
+**The `rule` field**
+The rule consists of max. 4 individual, space separated parameters:
+1. **type**: `feed` or `suricata` (required)
+ * `feed`: a plain IP/CIDR list, one entry per line (the common case)
+ * `suricata`: a Suricata/Snort-style ruleset; banIP extracts the IPs out of the rule lines
+2. **prefix**: an optional search term (a literal string, not a regex) that a line must contain to be considered a valid entry. Use it to skip comment/header lines or to pick only the relevant rows. Omit it if every data line is a bare IP.
+3. **column**: the 1-based column that holds the IP within a matching line, e.g. `1` for a bare list or `13` for the dshield block file (required)
+4. **separator**: an optional field separator; default is the whitespace character class `[[:space:]]`. Pass a literal character such as `,` for comma-separated sources (e.g. turris).
+
+**The `chain` field**
+Defines the default blocking direction. See the [Best practise feed-direction note](#best-practise-and-tweaks) for the reasoning behind each value:
+* `in`: inbound only (WAN-Input + WAN-Forward). Use for source-IP reputation feeds (attackers, scanners, spam senders).
+* `out`: outbound only (LAN-Forward). Use for destination feeds your own clients should never reach (malware hosts, C2, DoH/DNS endpoints).
+* `inout`: both directions. Use when the risk genuinely exists on both sides (e.g. spamhaus, tor, proxy, vpn).
+
+This is only the **default**; a user can always override it at runtime per feed via `ban_feedin` / `ban_feedout` / `ban_feedinout`, or strip a feed's port/protocol limitation via `ban_feedreset` — without touching the feed JSON.
+
+**The `flag` field (optional)**
+A space separated list of extra options:
+* `gz`: the source is gzip-compressed and will be decompressed before parsing (e.g. `backscatterer`, `ipthreat`, `uceprotect*`)
+* protocol/port limitation — one or both of `tcp` / `udp` followed by one or more destination ports or port ranges. This restricts the feed's rules to those destination ports, which is mainly useful for outbound feeds to cut false positives.
+Examples:
+ * `tcp udp 80 443`: limit to HTTP/HTTPS (e.g. `doh`, `hagezi`, `feodo`, `urlhaus`, `urlvir`, `webclient`)
+ * `tcp udp 53 853`: limit to plain DNS and DNS-over-TLS (e.g. `dns`)
+ * `tcp 22`: limit to SSH
+ * `tcp 5060-5061 udp 5060`: mix of single ports and ranges
+ * `gz` and a port limitation can be combined, e.g. `gz tcp udp 80 443`
+
+After editing `/etc/banip/banip.custom.feeds`, reload banIP (`/etc/init.d/banip reload`) and check the `Processing Log` tab — a malformed JSON object or a wrong column/separator typically shows up there as a feed that loads zero elements.