AJAX XML

JA JASWANT Updated 18 Oct 2026
5 min read ·Lesson 112 of 143
In this post

Exchanging XML With AJAX In simple terms: The "X" in AJAX originally stood for XML: early AJAX applications exchanged XML documents between browser and server. Today JSON is the usual choice, but XML

Exchanging XML With AJAX

In simple terms: The "X" in AJAX originally stood for XML: early AJAX applications exchanged XML documents between browser and server. Today JSON is the usual choice, but XML still appears — when integrating with older systems, consuming news feeds, or working with industry formats. This lesson shows both directions: PHP producing XML for JavaScript, and PHP receiving XML sent by JavaScript.

Technically: A PHP endpoint returns XML with the Content-Type: application/xml header, generated safely with XMLWriter, DOM, or SimpleXML. In the browser, fetch() reads the response as text, and the built-in DOMParser turns it into an XML document that can be queried with querySelector() or getElementsByTagName(). For XML sent to PHP, the endpoint reads the raw body from php://input and parses it with SimpleXML.

XML or JSON for AJAX?

JSONXML
Parsing in JavaScriptresponse.json() — native objectsDOMParser — DOM queries
Size and readabilityCompactMore verbose
Attributes, namespaces, schemasNoYes
Typical use todayNew APIs, most AJAXFeeds (RSS/Atom), legacy services, standardized formats

Use JSON unless an existing system or format requires XML.


PHP Producing XML: api/courses.xml.php

<?php
declare(strict_types=1);

$courses = [
    ['id' => 'php', 'title' => 'PHP Full Course', 'weeks' => 12, 'level' => 'Beginner'],
    ['id' => 'mysql', 'title' => 'MySQL Essentials', 'weeks' => 6, 'level' => 'Beginner'],
    ['id' => 'laravel', 'title' => 'Laravel Framework', 'weeks' => 10, 'level' => 'Intermediate'],
];

$level = $_GET['level'] ?? '';
if ($level !== '') {
    $courses = array_filter($courses, fn(array $course): bool => $course['level'] === $level);
}

header('Content-Type: application/xml; charset=utf-8');

$writer = new XMLWriter();
$writer->openUri('php://output');
$writer->setIndent(true);
$writer->setIndentString('  ');

$writer->startDocument('1.0', 'UTF-8');
$writer->startElement('courses');

foreach ($courses as $course) {
    $writer->startElement('course');
    $writer->writeAttribute('id', $course['id']);
    $writer->writeAttribute('level', $course['level']);
    $writer->writeElement('title', $course['title']);
    $writer->writeElement('weeks', (string) $course['weeks']);
    $writer->endElement();
}

$writer->endElement();
$writer->endDocument();
$writer->flush();

Response for api/courses.xml.php

<?xml version="1.0" encoding="UTF-8"?>
<courses>
  <course id="php" level="Beginner">
    <title>PHP Full Course</title>
    <weeks>12</weeks>
  </course>
  <course id="mysql" level="Beginner">
    <title>MySQL Essentials</title>
    <weeks>6</weeks>
  </course>
  <course id="laravel" level="Intermediate">
    <title>Laravel Framework</title>
    <weeks>10</weeks>
  </course>
</courses>

With ?level=Intermediate, only the Laravel course is included. In a real application, the courses come from the database. XMLWriter escapes all values automatically (see PHP XMLWriter); the level parameter is only compared, never written into the output unchecked.


JavaScript Reading XML

<select id="level">
    <option value="">All levels</option>
    <option value="Beginner">Beginner</option>
    <option value="Intermediate">Intermediate</option>
</select>
<ul id="course-list"></ul>

<script>
    const levelSelect = document.getElementById('level');
    const list = document.getElementById('course-list');

    async function loadCourses() {
        const params = new URLSearchParams({ level: levelSelect.value });
        const response = await fetch(`api/courses.xml.php?${params}`);

        if (!response.ok) {
            list.replaceChildren(new Text('Could not load courses.'));
            return;
        }

        const xml = new DOMParser().parseFromString(await response.text(), 'application/xml');

        if (xml.querySelector('parsererror')) {
            list.replaceChildren(new Text('The server sent invalid XML.'));
            return;
        }

        const items = [...xml.querySelectorAll('course')].map(course => {
            const li = document.createElement('li');
            const title = course.querySelector('title').textContent;
            const weeks = course.querySelector('weeks').textContent;
            li.textContent = `${title} — ${weeks} weeks (${course.getAttribute('level')})`;
            return li;
        });

        list.replaceChildren(...items);
    }

    levelSelect.addEventListener('change', loadCourses);
    loadCourses();
</script>

Result in the Browser (All Levels)

• PHP Full Course — 12 weeks (Beginner)
• MySQL Essentials — 6 weeks (Beginner)
• Laravel Framework — 10 weeks (Intermediate)

How It Works

  1. response.text() reads the body as a string; DOMParser with the type application/xml turns it into an XML document.
  2. Malformed XML does not throw: the parser returns a document containing a <parsererror> element, which must be checked explicitly.
  3. querySelectorAll('course'), querySelector('title'), textContent, and getAttribute() work on XML documents just as on HTML.
  4. List items are created with textContent, so course titles can never inject HTML.

JavaScript Sending XML to PHP

Some systems expect XML requests — for example, an order sent to a warehouse service. PHP reads the raw body and parses it:

<?php
// api/stock-check.php — receives <request><sku>…</sku>…</request>
declare(strict_types=1);

libxml_use_internal_errors(true);

$body = file_get_contents('php://input', length: 100_000);   // limit the size
$request = $body === false ? false : simplexml_load_string($body);

header('Content-Type: application/xml; charset=utf-8');

$response = new SimpleXMLElement('<response/>');

if ($request === false || $request->getName() !== 'request') {
    http_response_code(400);
    $response->error = 'Invalid XML request.';
    echo $response->asXML();
    exit;
}

$stock = ['PEN-01' => 118, 'LAMP-01' => 6, 'BAG-02' => 0];   // normally from the database

foreach ($request->sku as $sku) {
    $code = strtoupper(trim((string) $sku));
    $item = $response->addChild('item');
    $item->addAttribute('sku', $code);
    $item->addAttribute('available', isset($stock[$code]) && $stock[$code] > 0 ? 'yes' : 'no');
}

echo $response->asXML();

Request and Response

const response = await fetch('api/stock-check.php', {
    method: 'POST',
    headers: { 'Content-Type': 'application/xml' },
    body: '<request><sku>LAMP-01</sku><sku>BAG-02</sku></request>',
});
console.log(await response.text());
<?xml version="1.0"?>
<response><item sku="LAMP-01" available="yes"/><item sku="BAG-02" available="no"/></response>

Key Points

  • The body is read from php://input with a maximum length, protecting against oversized requests.
  • Parsing uses libxml's secure defaults; never add LIBXML_NOENT or LIBXML_DTDLOAD for request bodies (see PHP XML Parsers).
  • Every value is cast and normalized before use; the response is built with SimpleXML, so attribute values are escaped.
  • Invalid input produces a 400 response in the same XML format the client expects.

Serving External Feeds: A Server-Side Proxy

Browsers usually cannot fetch RSS feeds from other domains directly, because of the same-origin policy (see AJAX Intro). A PHP endpoint can fetch the feed on the server, extract the needed data, and return it as JSON:

<?php
// api/blog-feed.php (simplified)
$xml = simplexml_load_string(httpGet('https://example.com/feed.xml'));   // fetch with cURL and timeouts

$items = [];
foreach ($xml->channel->item as $item) {
    $items[] = ['title' => (string) $item->title, 'link' => (string) $item->link];
    if (count($items) === 5) {
        break;
    }
}

header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: public, max-age=600');
echo json_encode(['items' => $items], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

Only fetch fixed, trusted feed URLs — letting users pass arbitrary URLs to such a proxy creates a server-side request forgery (SSRF) risk. Cache the result, so the external site is not contacted on every page view.


Common Mistakes

Missing or Wrong Content-Type

Effect: Clients misinterpret the response.

Fix: Send application/xml; charset=utf-8 for XML responses.

Not Checking for <parsererror>

Effect: JavaScript processes an error document as if it were data.

Fix: Check xml.querySelector('parsererror') after parsing.

Building XML Responses With String Concatenation

Effect: Broken XML when values contain & or <.

Fix: Use XMLWriter, DOM, or SimpleXML.

Unlimited Request Bodies

Effect: Memory exhaustion from huge uploads.

Fix: Read php://input with a length limit and enforce server limits.


Best Practices

  • Prefer JSON for new AJAX features; use XML when a format or system requires it.
  • Generate XML with an XML API and send the correct content type.
  • Parse XML in the browser with DOMParser and check for parse errors.
  • Parse incoming XML in PHP with size limits, libxml's secure defaults, and full validation.
  • Use server-side proxies with fixed URLs and caching to integrate external feeds.

Official reference: https://www.php.net/manual/en/book.simplexml.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Home PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else Slicing Strings User-defined Functions Slicing Strings PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning
Ask about this post
AI Ask about this post

Ask questions about AJAX XML and get answers drawn from it.

Signed-in readers only.