Native-first image compression, resize, and format conversion for React Native.
Compress supported local images to JPEG, PNG, or WebP, with optional resize, quality, target-size, and metadata controls. Use the runtime capability API to handle platform codec differences before compression.
- Package version:
0.4.0 - Release target:
0.4.0 - Published npm latest:
0.4.0 - Release state:
release - Registry checked at:
2026-07-20
Version 0.4.0 is the current published release. It moves large image work to
bounded background workers, downsamples resize requests during decode, rejects
unsafe work before full decode, supports cancellation, and publishes only
complete transactional cache files. npm latest, immutable tag v0.4.0, and
the GitHub Release all resolve to the verified 0.4.0 artifact and source.
npm install react-native-image-compression-kitOr with pnpm:
pnpm add react-native-image-compression-kitFor iOS, install pods after adding the package:
cd ios && pod installAndroid 23+ and iOS 13.4+ are supported. The package declares React Native
>=0.73 <1.0. The v0.4.0 release-target matrix verifies React Native 0.73.11 Legacy,
React Native 0.86.0 Legacy and New Architecture, and Expo 57.0.7 with React
Native 0.86.0 New Architecture on both platforms. Versions between the tested
endpoints are accepted by the peer range but are not individually release
tested. Expo requires a development build or prebuild; Expo Go and Snack
cannot load this custom native module. See the
exact compatibility evidence.
See the installation guide for Bare React Native, Expo development-build, rebuild, and URI integration steps.
import {
compressImage,
getImageCompressionCapabilities,
} from 'react-native-image-compression-kit';
const capabilities = await getImageCompressionCapabilities();
const canWriteWebP = capabilities.formats.some(
({ format, output }) => format === 'webp' && output
);
const result = await compressImage({
source: { uri: imageUri },
resize: {
maxWidth: 2048,
maxHeight: 2048,
mode: 'contain',
},
output: {
format: canWriteWebP ? 'webp' : 'jpeg',
quality: 80,
},
metadata: 'safe',
});
console.log(result.uri, result.byteSize, result.width, result.height);Input must be a local URI accessible to the native app. Android supports
file:// and content://; iOS supports file:// and best-effort local
content:// loading.
Returns Promise<CompressionResult>.
interface CompressionOptions {
source: { uri: string };
resize?: {
maxWidth?: number;
maxHeight?: number;
mode?: 'contain' | 'cover' | 'stretch';
};
output: {
format: 'jpeg' | 'png' | 'webp' | 'heic' | 'heif' | 'avif';
quality?: number;
maxBytes?: number;
};
metadata?: 'preserve' | 'safe' | 'strip';
}
interface CompressionResult {
uri: string;
format: 'jpeg' | 'png' | 'webp' | 'heic' | 'heif' | 'avif';
width: number;
height: number;
byteSize: number;
originalByteSize: number;
compressionRatio: number;
}
interface CompressionControl {
signal: CompressionAbortSignal;
}The optional second argument accepts either an AbortSignal directly or a
CompressionControl object. Aborts before dispatch, while queued, or while
running reject with ERR_CANCELLED; aborting after completion is a no-op.
const controller = new AbortController();
const compression = compressImage(options, { signal: controller.signal });
controller.abort();
await compression;Returns the current platform's input/output format availability, metadata policies, target-size and cancellation support, bounded concurrency, decode-downsampling support, and named source/working pixel limits. Check it at runtime; codec support is not identical across Android versions, devices, and iOS runtimes.
ImageCompressionKitErrorImageCompressionKitErrorCodeIMAGE_FORMATS,OUTPUT_FORMATS,METADATA_POLICIES,RESIZE_MODES- Public TypeScript types for options, results, formats, resize, metadata, and capabilities
const result = await compressImage({
source: { uri: imageUri },
resize: { maxWidth: 1600, maxHeight: 1600, mode: 'contain' },
output: { format: 'jpeg', quality: 82 },
metadata: 'safe',
});const result = await compressImage({
source: { uri: imageUri },
output: { format: 'webp', quality: 90, maxBytes: 500_000 },
metadata: 'strip',
});quality is the upper bound when used with maxBytes. The native pipeline
searches for the highest supported quality under the target. It returns the
smallest generated result if the target cannot be reached. PNG does not support
maxBytes.
const result = await compressImage({
source: { uri: heicUri },
output: { format: 'jpeg', quality: 85 },
metadata: 'safe',
});import {
compressImage,
ImageCompressionKitError,
} from 'react-native-image-compression-kit';
try {
await compressImage(options);
} catch (error) {
if (error instanceof ImageCompressionKitError) {
console.warn(error.code, error.message);
}
}| Capability | Android | iOS |
|---|---|---|
| JPEG/PNG/WebP input | Yes | Yes; WebP is static ImageIO decode |
| GIF input | Static first frame | Static ImageIO decode |
| HEIC/HEIF input | SDK/device codec gated | Static ImageIO decode |
| AVIF input | Android 14+ (ImageDecoder) |
Runtime ImageIO source gated |
| JPEG output | Yes | Yes |
| PNG output | Yes | Yes |
| WebP output | Yes | Runtime ImageIO destination gated |
| HEIC/HEIF/AVIF output | Not implemented | Not implemented |
maxBytes |
JPEG and WebP | JPEG and runtime-available WebP |
| Resize modes | contain, cover, stretch |
contain, cover, stretch |
| Decode downsampling | BitmapFactory.inSampleSize / ImageDecoder target |
ImageIO thumbnail |
| Concurrent operations | Maximum 2 | Maximum 2 |
| Cancellation | Yes | Yes |
Important limitations:
- HEIC, HEIF, and AVIF output reject with
ERR_NOT_IMPLEMENTED. - GIF output and animation preservation for GIF/WebP/AVIF are not implemented.
metadata: 'preserve'is supported only for JPEG source to JPEG output.- Android
safecopies a privacy-filtered JPEG EXIF allowlist. iOSsafeandstripre-encode without copying source metadata. - JPEG orientation is rendered into pixels before resize/encode; preserved output orientation and dimensions are normalized.
- Sources above 32,768 pixels on either axis or 100,000,000 total pixels reject
with
ERR_RESOURCE_LIMIT. Work requiring more than 25,000,000 decoded pixels must provide smaller resize bounds. - JPEG output flattens transparency onto white on both platforms. PNG and WebP alpha capability reflects decode-back validation.
- Failed and cancelled operations remove temporary/partial cache files; a successful returned cache file retains the existing application ownership contract.
- Capability checks should drive fallbacks for SDK-, device-, and runtime-dependent codecs.
pnpm test:coverage
pnpm verify
pnpm example:typecheck
pnpm example:ios:decoder-test
pnpm example:ios:encoder-test
pnpm example:ios:output-test
pnpm example:ios:pipeline-test
pnpm example:ios:large-image-test
pnpm example:ios:metadata-test
pnpm example:ios:transformer-test
pnpm docs:check
pnpm site:check
pnpm site:build
pnpm fixtures:compatibility:check
git diff --check
pnpm pack --dry-runNative demo captures run a deterministic, capture-only walkthrough in the real Android and iOS example apps: bundled source, selected options, runtime capabilities, active compression, and the native before/after result. The evidence contract records the ordered stage timeline, MP4 duration and digest, exact source SHA, workflow run, runtime, and device.
The hosted recorders may omit repeated frames while a stage is static, so the
workflow normalizes the real captured frames to the separately logged
walkthrough duration with ffmpeg and holds the exact final native screenshot
for six seconds. The manifest records that capture method, while validation
reads the actual video track duration rather than trusting the container's
movie header.
The same runs emit versioned baseline and exact-plan comparison evidence with raw samples, fixture and plan digests, balanced execution positions, and median/p95 summaries. Comparison dependencies remain inside the private example application and outside the published package. See the benchmark methodology for its timing boundary, reproduction commands, adapter caveats, and comparison policy.
pnpm test:coverage runs the Vitest suite once with V8 coverage. The gate
includes executable TypeScript runtime modules and directly tested pure
scripts/*-core.mjs helpers, with rounded measured-baseline thresholds of 94%
statements, 83% branches, 98% functions, and 94% lines. React Native Codegen
and barrel entry wrappers, generated trees, fixtures, retained evidence, and
CLI entry wrappers stay outside that unit-coverage boundary because native,
package, fixture, or subprocess contract tests own them. pnpm verify runs
that coverage gate instead of running the same Vitest suite twice, then runs
the build, offline fixture and release-evidence replay gates, workflow supply
chain checks, and the Android repository doctor. pnpm docs:check is
network-free and validates the repository status manifest, aligned
README/RELEASE blocks, required documentation structure, local links/anchors,
and npm package exclusions.
For release-oriented changes, also run:
pnpm smoke:consumer
pnpm release:dry-runThe release dry run never publishes. Its shared state matrix blocks a
candidate and permits release only after package metadata, the release
target, and document mirrors are aligned. The separately tracked published npm
latest remains an observed registry value and is not rewritten before publish.
The weekly Registry Health workflow
runs every Monday at 03:17 UTC, reads publishedNpmLatest from
docs/release-status.json, runs the real npm registry smoke with npm 12.0.1,
and compares npm latest, exact-version metadata, the downloaded tarball,
README/package inventory, and clean consumer install/typecheck with
evidence/npm/<version>. It retains manual dispatch and limited pull-request
validation, has only contents: read, uses no protected environment, and
creates no provenance or attestation. The manual Registry Validation workflow
is separate: maintainers dispatch it through npm-production when fresh
provenance and attestation evidence is required.
Run the same read-only monitor locally without hardcoding the release version:
health_version="$(node -p "require('./docs/release-status.json').publishedNpmLatest")"
health_dir="$(mktemp -d)"
pnpm smoke:registry -- \
--version "$health_version" \
--expect-tag latest \
--json \
--artifact-dir "$health_dir/live"
pnpm verify:registry-health -- \
--live-artifact-dir "$health_dir/live" \
--json \
--report-file "$health_dir/registry-health.json"The canonical report compares package name, requested/resolved/tagged version,
publish timestamp, tarball URL, SRI, shasum, SHA-256, packed bytes, file count,
unpacked bytes, README status, forbidden files, and consumer smoke result. A
failure is an investigation signal only: it grants no authority to republish,
change a dist-tag, move a Git tag, or edit a GitHub Release. Inspect the report
drift first, then the release-status handoff and matching evidence archive,
then exact npm metadata and a fresh smoke. A release handoff is complete only
when docs/release-status.json and evidence/npm/<version> identify the same
published version.
Operational material is repository-only and is not included in the npm tarball:
See SECURITY.md for supported versions, private vulnerability reporting, package prohibitions, and repository-only execution procedures.
MIT License. See LICENSE.