apidocs

UseFormTemplate API

Returns the rendered HTML form for a content template. The response is the complete HTML that the user’s browser can display and submit to create a new document in the specified destination folder.

Endpoint

/srv.asmx/UseFormTemplate

Methods

Parameters

Parameter Type Required Description
authenticationTicket string Yes Authentication ticket obtained from AuthenticateUser.
targetFolderPath string Yes Full infoRouter path of the destination folder where the new document will be created (e.g. /Finance/Reports).
templatePath string Yes Full infoRouter path of the template document (e.g. /Templates/ExpenseForm.htm), or ~D<id> short form (e.g. ~D42). Use ~D999 for the built-in HTML document type.
submitUrl string Yes URL to set as the HTML form action attribute. Pass an empty string "" to use the default legacy IRDOC.ASPX handler. Pass the route of a React/SPA page to intercept the submission client-side instead. This parameter must always be present in the request; omitting it entirely from the query string or form body will cause a server error.

Response

Success Response

<root success="true"><![CDATA[
<!DOCTYPE html>
<html>
  ...rendered form HTML...
</html>
]]></root>

The element content is the complete rendered HTML form wrapped in a CDATA section to preserve HTML characters (<, >, &) without XML encoding.

Error Response

<root success="false" error="Error message" />

Required Permissions


Example

POST Request

POST /srv.asmx/UseFormTemplate HTTP/1.1
Content-Type: application/x-www-form-urlencoded

authenticationTicket=3f2504e0-4f89-11d3-9a0c-0305e82c3301
&targetFolderPath=/Finance/Reports
&templatePath=/Templates/ExpenseReport.htm
&submitUrl=

Using ~D999 for HTML Documents

To render the built-in HTML document form (not tied to a specific template file):

POST /srv.asmx/UseFormTemplate HTTP/1.1
Content-Type: application/x-www-form-urlencoded

authenticationTicket=3f2504e0-4f89-11d3-9a0c-0305e82c3301
&targetFolderPath=/Finance/Reports
&templatePath=~D999
&submitUrl=

Using submitUrl for React/SPA Integration

Do not use submitUrl when embedding inside a React app via iframe — see the React section below. The submitUrl parameter is only useful when an external page submits to a known server-side route.


React Integration

The rendered HTML is a complete standalone page (<html>, <head>, <style>, <body>). The correct way to embed it in a React application is inside an <iframe srcdoc>, not via dangerouslySetInnerHTML.

Why iframe and not dangerouslySetInnerHTML:

  dangerouslySetInnerHTML <iframe srcdoc>
<script> tags execute No Yes
<style> isolated from React app No — leaks into host page Yes
Full HTML document supported No — strips <html>/<head>/<body> Yes
Submit interception Requires querySelector on container contentDocument.querySelector on load

srcdoc iframes are always same-origin with their parent, so contentDocument access is always permitted regardless of the host the React app is served from.

The rendered HTML has InfoRouter_Ticket set to an empty string. The React app must inject the live session ticket in the onLoad handler before the user submits.

Step 1 — Fetch the form

const res = await fetch(
  `/srv.asmx/UseFormTemplate?authenticationTicket=${ticket}` +
  `&targetFolderPath=${encodeURIComponent(targetFolderPath)}` +
  `&templatePath=${encodeURIComponent(templatePath)}` +
  `&submitUrl=`
);
const xml = new DOMParser().parseFromString(await res.text(), 'text/xml');
const renderedHtml = xml.querySelector('root').textContent;

Step 2 — Render inside an iframe and intercept submit

import { useRef } from 'react';

function InfoRouterForm({ renderedHtml, authTicket, targetFolderPath, documentName }) {
  const iframeRef = useRef(null);

  const handleLoad = () => {
    const doc = iframeRef.current.contentDocument;
    const form = doc.querySelector('form');
    if (!form) return;

    // Inject the live ticket — the rendered HTML has this field empty
    const ticketField = doc.getElementById('InfoRouter_Ticket');
    if (ticketField) ticketField.value = authTicket;

    form.addEventListener('submit', async (e) => {
      e.preventDefault();

      // Parse InfoRouter_Fields to discover field names and build xmlContent
      const fieldsInput = doc.getElementById('InfoRouter_Fields');
      const tokens = fieldsInput ? fieldsInput.value.split(',') : [];
      let xmlContent = '<FORMDATA>';
      for (let i = 0; i + 3 < tokens.length; i += 4) {
        const raw   = tokens[i].trim();                 // e.g. "'IR_title'"
        const name  = raw.slice(4, raw.length - 1);    // strip 'IR_ prefix and trailing '
        const value = (form.elements[name]?.value ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
        xmlContent += `<Prompt Name="${name}">${value}</Prompt>`;
      }
      xmlContent += '</FORMDATA>';

      const body = new URLSearchParams({
        AuthenticationTicket: authTicket,
        Path:         targetFolderPath.replace(/\/+$/, '') + '/' + documentName,
        TemplatePath: templatePath,
        xmlContent,
      });

      const res = await fetch('/srv.asmx/CreateDocumentUsingTemplate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: body.toString(),
      });

      const xml = new DOMParser().parseFromString(await res.text(), 'text/xml');
      const root = xml.querySelector('root') ?? xml.querySelector('response');
      if (root?.getAttribute('success') === 'true') {
        // handle success — root may have DocumentID and DocumentName attributes
      } else {
        console.error('Submit failed:', root?.getAttribute('error'));
      }
    });
  };

  return (
    <iframe
      ref={iframeRef}
      srcdoc={renderedHtml}
      onLoad={handleLoad}
      style=
      title="Document Form"
    />
  );
}

The InfoRouter_Fields hidden input

The rendered HTML always contains a hidden input named InfoRouter_Fields. Its value is a comma-separated list of 4-token descriptors — one group per template field:

'IR_title','CHAR','N','N','IR_author','CHAR','Y','N','IR_duedate','DATE','Y','N'

Each group of 4 tokens:

Position in group Meaning
0 — 'IR_{fieldname}' HTML input name with 'IR_ prefix and trailing '. Strip those to get the bare field name.
1 — 'CHAR' | 'DATE' | 'NUMBER' | 'BOOLEAN' Data type.
2 — 'Y' | 'N' Required flag.
3 — 'N' Reserved.

You must parse this value to know which form inputs to collect and to build the correct xmlContent for CreateDocumentUsingTemplate.

Key points


Notes



Error Codes

Error Description
[900] Authentication failed Invalid or missing authentication ticket.
[901] Session expired or Invalid ticket The ticket has expired or does not exist.
Folder not found The targetFolderPath does not exist or is not accessible.
Document not found The templatePath does not resolve to an existing document.
Access denied The user lacks Read permission on the template or Add Document permission on the folder.
SystemError:... An unexpected server-side error occurred.

React Implementer Guide

Production-ready patterns derived from the reference demo at IRWebCore/wwwRoot/form-template-demo.html.

Shared helper functions

These functions are used across all three form APIs (UseFormTemplate, GetFormFromDocument, CreateDocumentUsingTemplate). Define them once in a shared module.

// XML-escape a value before placing it inside a <Prompt> element
function escapeXml(s) {
  return String(s ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

// Parse the InfoRouter_Fields hidden input from a rendered form inside an iframe.
// Returns an array of { name, value, dataType, required }.
// InfoRouter_Fields format: 'IR_field1','CHAR','N','N','IR_field2','DATE','Y','N',...
function parseInfoRouterFields(iframeDoc, form) {
  const input = iframeDoc.getElementById('InfoRouter_Fields');
  if (!input || !input.value.trim()) return [];
  const tokens = input.value.split(',');
  const fields = [];
  for (let i = 0; i + 3 < tokens.length; i += 4) {
    const raw      = tokens[i].trim();                       // e.g. "'IR_title'"
    if (raw.length < 5) continue;
    const name     = raw.slice(4, raw.length - 1);           // strip 'IR_ prefix + trailing '
    const dataType = tokens[i + 1].trim().replace(/'/g, ''); // CHAR | DATE | NUMBER | BOOLEAN
    const required = tokens[i + 2].trim() === "'Y'";
    const el       = form.elements[name];
    const value    = el ? el.value : '';
    fields.push({ name, value, dataType, required });
  }
  return fields;
}

// Build the <FORMDATA> XML required by CreateDocumentUsingTemplate
function buildXmlContent(fields) {
  if (!fields || fields.length === 0) return '';
  return '<FORMDATA>' +
    fields.map(f => `<Prompt Name="${f.name}">${escapeXml(f.value)}</Prompt>`).join('') +
    '</FORMDATA>';
}

// Parse an infoRouter XML response — handles both <root> and <response> root elements
function parseXml(xmlText) {
  const doc = new DOMParser().parseFromString(xmlText, 'text/xml');
  const root = doc.querySelector('root') ?? doc.querySelector('response');
  if (!root) return { ok: false, error: 'Could not parse response' };
  return { ok: root.getAttribute('success') === 'true', error: root.getAttribute('error') ?? '', el: root };
}

Step 1 — Fetch the form

async function loadForm(apiBase, ticket, targetFolderPath, templatePath) {
  const body = new URLSearchParams({
    authenticationTicket: ticket,
    targetFolderPath,
    templatePath,
    submitUrl: '',   // Always include; pass '' when using iframe submit interception
  });
  const res = await fetch(`${apiBase}/UseFormTemplate`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: body.toString(),
  });
  const r = parseXml(await res.text());
  if (!r.ok) throw new Error(r.error);
  return r.el.textContent;  // Raw HTML extracted from CDATA
}

r.el.textContent extracts the HTML from inside the CDATA wrapper. submitUrl must always be present — pass '' to let the iframe onLoad handler own submit interception.

Step 2 — Render in React and intercept submit

import { useState, useRef, useCallback } from 'react';

function CreateDocumentForm({ ticket, targetFolderPath, templatePath, apiBase, onSubmit }) {
  const [renderedHtml, setRenderedHtml] = useState('');
  const iframeRef = useRef(null);

  async function handleLoadForm() {
    const html = await loadForm(apiBase, ticket, targetFolderPath, templatePath);
    setRenderedHtml(html);
  }

  // useCallback with full dependency array prevents stale closures in the submit handler
  const handleIframeLoad = useCallback(() => {
    const iframe = iframeRef.current;
    if (!iframe || !renderedHtml) return;
    const doc = iframe.contentDocument;

    // Always inject the live ticket — the rendered form leaves InfoRouter_Ticket empty
    const ticketField = doc.getElementById('InfoRouter_Ticket');
    if (ticketField) ticketField.value = ticket;

    const form = doc.querySelector('form');
    if (!form) return;

    form.addEventListener('submit', e => {
      e.preventDefault();
      e.stopPropagation();
      // Collect all template fields and their current values
      const fields = parseInfoRouterFields(doc, form);
      // Hand off to the parent — it will call CreateDocumentUsingTemplate
      onSubmit({ fields, templatePath });
    });
  }, [renderedHtml, ticket, templatePath]);

  return (
    <>
      <button onClick={handleLoadForm}>Load Form</button>
      {renderedHtml && (
        <iframe
          ref={iframeRef}
          srcDoc={renderedHtml}
          onLoad={handleIframeLoad}
          sandbox="allow-scripts allow-forms allow-same-origin"
          style=
          title="Document Form"
        />
      )}
    </>
  );
}

Critical rules

Rule Reason
Use srcDoc (not src) on the iframe Renders the HTML string directly without a round-trip
sandbox="allow-scripts allow-forms allow-same-origin" Scripts inside the form execute; contentDocument access is permitted
e.preventDefault() + e.stopPropagation() Prevents the browser from navigating away on submit
Inject InfoRouter_Ticket in onLoad The rendered HTML always has this field empty by design
List [renderedHtml, ticket, templatePath] in useCallback deps Prevents the submit handler from capturing stale values when inputs change
Call CreateDocumentUsingTemplate from the onSubmit handler UseFormTemplate only renders the form; the actual save is a separate call