Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

“RNHTMLtoPDF error: Could not create folder structure” is not a diagnosis of one universal failure. It is a symptom raised while react-native-html-to-pdf is preparing or writing the PDF. Start by checking the directory and fileName options, the app’s storage context, and the exact path returned by generatePDF. Then compare your Android, React Native, and package versions and read the native log for a second, more specific error.

The most reliable fix is therefore a sequence of checks, not blindly adding a permission or changing Gradle settings.

What the error actually tells you

The message means the PDF output step could not create or use the folder it selected. It does not prove that the parent directory is missing, that Android storage permission is the cause, or that your HTML is invalid. A 2020 issue contains reports from several Android and React Native configurations, including one report with IllegalArgumentException: fd cannot be null. That makes the full native stack trace important: a folder message can appear alongside a later file-descriptor or converter-write failure.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The package’s README documents directory as the output directory option and says the cache directory is the default when you do not supply one. It also says that Documents is the only custom directory value accepted on iOS. Match those statements to the version installed in your app; option names and behavior can change between releases.

1. Record the environment before changing code

Write down the values for the failing build. The exact-error reports cover different combinations, so a fix reported for one setup cannot be assumed to apply to another.

  • Android API level on the device or emulator.
  • App target SDK and compile SDK.
  • React Native version (the issue report uses “React Native 0.63.x” in one configuration).
  • The installed react-native-html-to-pdf version.
  • Whether the failure occurs on Android, iOS, or both.
  • The complete JavaScript error and the native log around the same timestamp.

Reproduce once with a debug build after recording these values. This gives you a before-and-after comparison if an option or permission change is tested.

2. Verify the options against your installed package

Begin with the smallest documented call. Do not copy an example from a different release without checking that release’s README or API declaration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import RNHTMLtoPDF from 'react-native-html-to-pdf';

const html = `
  <html>
    <body>
      <h1>Invoice 1007</h1>
      <p>Generated by the mobile app.</p>
    </body>
  </html>
`;

try {
  const file = await RNHTMLtoPDF.generatePDF({
    html,
    fileName: 'invoice-1007',
    base64: false
  });

  console.log('PDF result:', file);
  console.log('PDF path:', file.filePath);
} catch (error) {
  console.error('RNHTMLtoPDF failed:', error);
}

With no directory supplied, the project README says the cache directory is used. That is a useful baseline because it avoids testing a custom location and the converter at the same time.

When to set directory

Add a directory only when you have a documented reason and a value accepted by your installed version:

import { Platform } from 'react-native';
import RNHTMLtoPDF from 'react-native-html-to-pdf';

const options = {
  html: '<h1>Report</h1>',
  fileName: 'report-2026-09-29',
  base64: false
};

// The README documents Documents as the only custom directory on iOS.
if (Platform.OS === 'ios') {
  options.directory = 'Documents';
}

const file = await RNHTMLtoPDF.generatePDF(options);
console.log(file.filePath);

On Android, do not infer that a string such as Download means the public, user-visible Downloads folder. Use the value documented for your package version, then inspect the returned path.

Check the file name as well

Use a simple name while diagnosing: letters, numbers, hyphens, and one extension-free base name such as report-42. Avoid slashes, a manually supplied .pdf suffix unless your version documents it, and names that several concurrent jobs reuse. Once a basic conversion succeeds, reintroduce your production naming scheme.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Inspect the path that the library really returned

After a successful conversion, log file.filePath and pass that exact value to your viewer, share sheet, upload routine, or file operation. A related Android repository report returned a path resembling an app-specific location under Android/data/.../files/Download, even though the developer expected the shared public Downloads directory.

const file = await RNHTMLtoPDF.generatePDF({
  html,
  fileName: 'diagnostic-report',
  base64: false
});

if (!file || typeof file.filePath !== 'string' || file.filePath.length === 0) {
  throw new Error('RNHTMLtoPDF returned no usable filePath');
}

console.log('Use this exact path for the next operation:', file.filePath);

If your next operation says “file not found,” compare its input with the logged value character for character. Also check whether the operation runs before the conversion promise resolves. A directory label is not proof of a public shared location; the returned absolute path is the fact to test on the target device.

4. Test Android access as a current runtime fact

Several users in the 2020 exact-error issue reported that storage permission resolved their case. One report, using React Native 0.63, said a runtime request was needed. These are historical user outcomes, not a current Android recommendation. Permission behavior depends on the device, API level, target SDK, and the permission declarations in your app.

Check the following rather than adding old snippets automatically:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Is the app requesting the permission that its current Android configuration actually uses?
  • Does the runtime request return granted on the failing device?
  • Are you testing a debug build and a release build with the same manifest and target SDK?
  • Does the package’s documented directory require access that the default cache path does not?

If permission is part of your design, log the result and handle denial explicitly. Do not treat a manifest entry alone as proof that runtime access was granted. Verify the result on the exact API level where the conversion fails.

5. Read the native log for a more specific failure

When the JavaScript exception remains generic, collect the native stack trace from the same attempt. On Android, filter logcat around the conversion time and search for the first meaningful cause, not only the final “Could not create folder structure” line.

adb logcat -c
adb logcat | grep -i -E "html|pdf|fd cannot be null|folder|storage|exception"

The issue thread includes IllegalArgumentException: fd cannot be null. That points to a file-descriptor or write-path problem that needs separate investigation. Capture the complete trace, including the first “Caused by” section, and determine whether the folder message is the initial failure or a later wrapper exception.

Interpret common log patterns

  • Only a directory or permission error: recheck directory, the returned path, and the actual permission result.
  • fd cannot be null or another stream error: investigate the file-opening/writing stage and the exact path; changing HTML alone is unlikely to explain it.
  • Timeout, crash, or renderer exception before a path is returned: reduce the HTML to a minimal document, then add external assets one at a time.

6. Treat legacy compatibility flags and downgrades as historical experiments

One user in the 2020 issue reported success after adding android:requestLegacyExternalStorage="true" on API 29 and above. Another commenter questioned its temporary status. The available evidence does not establish whether that flag applies to your current target SDK, so it should not be copied as a general fix. If you investigate it, record the exact target SDK, test on a disposable branch, and remove it unless your current Android and package documentation explicitly requires it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Likewise, a report mentioned downgrading React Native and Gradle. That is a single setup’s history, not evidence that downgrading is appropriate for every app. Prefer aligning the package with your supported React Native and Android toolchain, then test a minimal conversion before considering a rollback.

A repeatable diagnostic procedure

  1. Reproduce with the default directory. Remove custom directory settings and use a short, unique file name.
  2. Use minimal HTML. Generate a document containing one heading and one paragraph, with no remote images, fonts, scripts, or unusual CSS.
  3. Log the complete result. Record the object returned by generatePDF, especially filePath.
  4. Open or share that exact path. Do not reconstruct a path from a directory label.
  5. Add one variable at a time. Test a custom directory, then production HTML, then your downstream file operation.
  6. Compare platforms. If iOS works with Documents but Android fails, keep the platform-specific behavior separate instead of reusing the same directory value.
  7. Capture native logs. Preserve the full trace if the generic message returns.
  8. Retest after every environmental change. Note the device API, build type, target SDK, React Native version, and package version for each result.

Reliability and operational notes

Use unique names for repeated jobs

During testing, add an identifier or timestamp to each fileName. This prevents you from mistaking an older PDF for the newly generated one and helps reveal whether concurrent conversions are colliding.

Keep conversion and file use sequential

Await generatePDF before starting upload, sharing, preview, or deletion. Pass the returned filePath directly to the next step and log failures from that step separately.

Separate converter failures from HTML failures

A minimal HTML document distinguishes output-path problems from rendering problems. Reintroduce remote images, custom fonts, JavaScript, and large tables only after the minimal case succeeds. This also makes native logs easier to read.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test the build you ship

Debug and release builds can differ in manifests, packaging, and runtime configuration. Re-run the same path and permission checks in the release-like build used by testers or customers.

Troubleshooting quick reference

Symptom Likely area to check Next action
Error appears immediately with a custom directory Unsupported or incorrectly named directory option Remove directory, confirm the installed version’s options, then add only a documented value.
Conversion resolves, but the viewer cannot find the PDF Wrong assumed location Log file.filePath and use that exact path.
Android works on one API level but not another Version- or permission-dependent behavior Record API level, target SDK, runtime permission result, and package version for both devices.
Native log contains fd cannot be null File descriptor or write stage Investigate the complete native trace and path handling instead of treating it as only a folder problem.
Only complex documents fail HTML or asset rendering Reduce to minimal HTML, then add assets incrementally.
A legacy flag appears to fix one old device Historical compatibility behavior Validate against your current target SDK and documentation before keeping it.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your workflow also needs a clean image or PDF of a web page—for example, to attach a reference to a generated report—ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. It is separate from RNHTMLtoPDF, so it does not repair the native PDF conversion error; it can remove a browser-capture step around your surrounding workflow.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Before capture, cookie/consent banners, newsletter popups, and chat widgets are removed. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients. The ScreenshotNeo documentation lists the options, including full-page capture, CSS-selector elements, device presets, PDF settings, custom JavaScript, waits, blocking rules, cookies, headers, geolocation, signed links, webhooks, bulk capture, and usage reporting. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does setting directory: 'Documents' fix Android?

Not necessarily. The README specifically documents Documents as the only custom directory value on iOS. On Android, verify the value supported by your installed package and use the returned path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I always request storage permission?

No universal rule is established by the issue reports. Check the permission result and your current API level, target SDK, and chosen output location before deciding whether your app needs a request.

Can a successful conversion still produce an unusable PDF?

Yes. A conversion can return a path while a later viewer, share operation, or upload uses a different path. Log and propagate file.filePath through the entire follow-up flow.

Is the legacy-storage flag a supported long-term solution?

The available evidence is a 2020 user report, not current platform guidance. Treat it as a historical experiment that requires validation against your present Android and target-SDK configuration.

Frequently Asked Questions

Does setting directory: 'Documents' fix Android?

Not necessarily. The README specifically documents Documents as the only custom directory value on iOS. On Android, verify the value supported by your installed package and use the returned path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I always request storage permission?

No universal rule is established by the issue reports. Check the permission result and your current API level, target SDK, and chosen output location before deciding whether your app needs a request.

Can a successful conversion still produce an unusable PDF?

Yes. A conversion can return a path while a later viewer, share operation, or upload uses a different path. Log and propagate file.filePath through the entire follow-up flow.

Is the legacy-storage flag a supported long-term solution?

The available evidence is a 2020 user report, not current platform guidance. Treat it as a historical experiment that requires validation against your present Android and target-SDK configuration.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.