xml_decode()
Turn an XML string into a nested array. Use it for RSS and Atom feeds, and for APIs that answer in XML instead of JSON.
It is the XML counterpart to fromjson(). The name is xml_decode because xml_parse is already taken by a different, low-level PHP function.
Syntax
xml_decode(xml, list_tags)
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
xml |
string | yes | The XML text, for example the body of a curl_get() response. |
list_tags |
string or array | no | Names of text-only tags that should always be a list. Only needed for the case described in "Text tags that repeat" below. |
Returns
A nested array, or null if the text is empty or is not valid XML (the reason is written to the run log).
How elements map:
| XML | Result |
|---|---|
<title>News</title> |
the string "News" |
<empty/> |
an empty string |
<entry>...</entry> (has children) |
a list, even if there is only one |
<link href="http://x/"/> (has attributes) |
a list: ["@attributes" => ["href" => "http://x/"](/help/n-a)] |
<content type="html">Hi</content> |
[["@attributes" => ["type" => "html"], "@text" => "Hi"]] |
<media:thumbnail/> |
keyed by the full name, "media:thumbnail" |
The outer element is unwrapped: for a feed whose root is <feed>, the array you get back is the contents of <feed>. Text is trimmed, and CDATA blocks are unwrapped for you.
Any element with children or attributes is a list
XML has no way to mark something as a list. A list is just the same tag written more than once, so a parser that counts occurrences hands you a different shape depending on how many results came back: your loop works all month, then a quiet week returns one item and the loop silently walks that item's own fields instead.
xml_decode() does not count. Any element that has children or attributes is always a list, so one result and fifty results read exactly the same:
$feed = xml_decode($resp["body"]);
foreach ( $feed["entry"] as $entry ) {
sys_log($entry["title"]);
}
That is why the [0] appears on link below: it has an attribute, so it is a list of one.
Plain text elements stay plain, so titles, ids and dates read normally:
$entry["title"] // "Bending Spoons acquisition"
$entry["link"][0]["@attributes"]["href"] // "https://www.reddit.com/r/..."
$entry["author"][0]["name"] // "/u/example"
Text tags that repeat
The one thing left to watch is a text-only tag that can appear more than once, such as <category>news</category> in RSS. Alone it is the string "news"; twice it becomes ["news", "tech"]. Name it in list_tags and it is always a list:
$feed = xml_decode($resp["body"], "category");
$feed = xml_decode($resp["body"], ["category", "keyword"]); // several
Tags with attributes or children never need this.
Example
Read a Reddit search feed and create a record for each post:
$resp = curl_get("https://www.reddit.com/r/nocode/search.rss?q=airtable&restrict_sr=1&sort=new");
$feed = xml_decode($resp["body"]);
foreach ( $feed["entry"] as $entry ) {
record_create("Mentions", [
"title" => $entry["title"],
"url" => $entry["link"][0]["@attributes"]["href"],
"author" => $entry["author"][0]["name"],
"posted" => date_to_utc($entry["updated"])
]);
}
Example output
[
"title" => "search results",
"updated" => "2026-08-22T00:00:00+00:00",
"entry" => [
[
"title" => "Anyone else looking at alternatives?",
"id" => "t3_abc",
"link" => [ ["@attributes" => ["href" => "https://www.reddit.com/r/nocode/comments/abc/"]] ],
"author" => [ ["name" => "/u/example"] ],
"content" => [ ["@attributes" => ["type" => "html"], "@text" => "<p>...</p>"] ],
"updated" => "2026-08-20T12:00:00+00:00"
]
]
]
RSS 2.0
RSS puts the posts one level down, inside the channel. The channel has children, so it is a list of one:
$feed = xml_decode($resp["body"]);
foreach ( $feed["channel"][0]["item"] as $item ) {
sys_log($item["title"] . " " . $item["link"]);
}
Notes
- Feeds often wrap HTML in the
contentordescriptionelement. Run it through strip_tags_gf() before storing it as plain text. - Feed dates are usually already UTC. Store them with date_to_utc() if the feed carries a local offset.
- External entities and DTD references are ignored, so a hostile document cannot make your automation read files or fetch URLs.
- api_request(), curl_request(), and the other HTTP functions already decode an XML response body into
response["json"]when the server sends an XML content type. That decoding is older and uses a different, looser shape (single elements are not listed, and an attribute is dropped when the element also has text). If you want the shape described on this page, passresponse["body"]toxml_decode()yourself.
See also: curl_get(), strip_tags_gf(), array_search_col()