Settings

Every option, its valid values, and its default.

Any option with a fixed set of valid string values (frame.shape, mouseWheelZoom, zoomerPosition, and export()/upload()'s type/format) is validated at runtime, not just via TypeScript types. A typo like zoomerPosition: "bttom" logs a console.warn naming the bad value and the valid options, then falls back to the default — it never silently misbehaves.

Constructor options

new Kiri(container, {
  frame: {
    shape: "circle",          // "rectangle" | "circle" | "rounded-rectangle"
    width: 200,
    height: 200,
    cornerRadius: 20,          // only used when shape is "rounded-rectangle"
  },
  minZoom: 1,
  maxZoom: 4,
  rotatable: true,
  flippable: true,
  resizableFrame: false,
  lockAspectRatio: false,      // only matters when resizableFrame is true
  movableFrame: false,        // invert interaction: freeze the image, move the frame instead
  mouseWheelZoom: true,       // or "ctrl" to require Ctrl+wheel
  useExifOrientation: true,
  autoSizeStage: true,
  showZoomer: false,
  zoomerPosition: "bottom",   // "top" | "bottom" | "left" | "right"
  filters: { brightness: 1, contrast: 1, saturation: 1, sharpness: 1, grayscale: false, sepia: false },
  uploader: undefined,
});
OptionTypeValid valuesDefaultDescription
frame.shapestring "rectangle", "circle", "rounded-rectangle""rectangle" The shape of the crop selection. "circle" and "rounded-rectangle" are real clips on export too (transparent corners on PNG/WebP), not just a visual overlay — see Architecture.
frame.widthnumberany positive pixel size200 Width of the crop selection, in pixels. This is also the default output width for export().
frame.heightnumberany positive pixel size200 Height of the crop selection, in pixels. Also the default output height for export().
frame.cornerRadiusnumberany non-negative pixel size20 Only matters when frame.shape is "rounded-rectangle". The corner radius, in pixels, of both the on-screen frame and the export clip. If the output size passed to export() differs from frame.width/height, the radius scales proportionally so it still looks the same.
minZoomnumberany positive number ≤ maxZoom1 The lower bound on zoom — how far the user (or setZoom()) can zoom out. 1 means "the image is only ever as small as needed to fully cover the frame" (its natural minimum); you can't go smaller than that regardless of this setting. Raise it (e.g. 1.5) to force the image to always fill more of the frame than its bare minimum.
maxZoomnumberany positive number ≥ minZoom4 The upper bound on zoom — how far the user (or setZoom()) can zoom in. Lower it to stop people zooming in so far the exported image looks pixelated.
rotatablebooleantrue, falsetrue When true, calling cropper.rotate(90) actually rotates the image 90°, and you can call it repeatedly to keep rotating. When false, rotate() becomes a silent no-op — nothing happens, no error — which is useful if you want to hide/disable a rotate button in your own UI and guarantee rotation can't happen even if that call is somehow still made. It has no effect on drag/zoom/flip — those keep working either way.
flippablebooleantrue, falsetrue Same idea as rotatable, but for flipHorizontal()/flipVertical(). When false, both become no-ops.
resizableFramebooleantrue, falsefalse When true, a small drag handle appears at each of the frame's four corners — dragging one calls setFrameSize() for you, live, letting the end user resize the crop selection with the mouse/touch instead of only via code. See the resizable frame example.
lockAspectRatiobooleantrue, falsefalse Only matters when resizableFrame is also true. When true, dragging a corner handle preserves the frame's aspect ratio (whatever it was when the drag started) instead of letting width and height resize independently — useful for a fixed shape like a 1:1 avatar or a 16:9 thumbnail that the end user can still resize.
movableFramebooleantrue, falsefalse Inverts the interaction model. When true, the image is displayed at a fixed size and never pans or zooms — setZoom()/setOffset() become no-ops, and mouse wheel/pinch zoom stop doing anything. Dragging (or arrow keys) moves the frame over the static image instead, via setFramePosition(). Combine with resizableFrame to also resize the frame in place — it's capped at the image's own bounds instead of growing the image to compensate, since there's no auto-zoom left to absorb the difference. rotate()/flip still work, transforming the whole static image.
mouseWheelZoomboolean | stringtrue, false, "ctrl"true Whether scrolling the mouse wheel (or a trackpad pinch) over the stage zooms the image. false disables it entirely (zoom only via setZoom() or a showZoomer slider). "ctrl" requires holding the Ctrl key while scrolling to zoom — useful on a page where you also want normal page-scroll to work when the cursor happens to be over the cropper.
useExifOrientationbooleantrue, falsetrue When true, load() reads EXIF orientation data from a File/Blob (e.g. a photo straight off a phone camera) and automatically applies the correct rotation/flip so it displays right-side up. Set false to skip that check (e.g. if you've already normalized images server-side).
autoSizeStagebooleantrue, falsetrue When true, the stage (the visible box) sizes itself to the frame's dimensions plus a small margin — you don't need any CSS on the container element for it to look right. Set false if you'd rather the stage fill its container via your own CSS instead (100% width/height) — see Architecture.
showZoomerbooleantrue, falsefalse When true, Kiri renders its own <input type="range"> zoom slider next to the stage — no manual wiring needed. See the zoom slider example.
zoomerPositionstring"top", "bottom", "left", "right""bottom" Only matters when showZoomer: true. Which side of the stage the slider sits on — purely a placement choice, identical zoom behavior in every position.
filters.*—see Filters— Initial brightness/contrast/saturation/grayscale/sepia values — same shape as setFilters() takes. See Filters.
uploaderfunction(blob, options & {url}) => Promise<unknown>none — falls back to the built-in FormData/fetch uploader Overrides how upload() actually sends the crop — plug in your own function (e.g. to hit a presigned-URL flow or a GraphQL mutation) instead of the default FormData POST. Can also be overridden per-call via UploadOptions.uploader.

autoSizeStage: when true (default), the stage sizes itself to the frame's dimensions plus a 20px margin on each axis — no CSS required on the container. Set false to have the stage fill its container instead (100% width/height via CSS), for embedding in a layout where you want to control the stage's size directly.

load() options

Lets you open an image already zoomed/panned/rotated a certain way, instead of always starting from scratch — useful for restoring a previously-saved crop state.

OptionTypeValid valuesDefaultDescription
zoomnumberclamped to [minZoom, maxZoom]minZoom Starting zoom level, as if setZoom() had been called right after loading.
offset.x / offset.ynumberclamped so the frame stays covered0 Starting pan position, in pixels from center. Clamped automatically so the frame can never show past the image's edge.
rotationnumber (degrees)snapped to the nearest 90°0 Starting rotation, as if rotate() had already been called that many degrees.
flip.horizontal / flip.verticalbooleantrue, falsefalse Starting flip state, as if flipHorizontal()/flipVertical() had already been called.

export() / upload() options

upload(url, options) takes everything export() does, plus a few upload-only fields.

OptionTypeValid valuesDefaultDescription
typestring"base64", "blob", "canvas""base64" What shape the result comes back as: a data URL string (drop straight into an <img src>), a Blob (for FormData/upload()), or a raw HTMLCanvasElement (for further canvas manipulation).
formatstring"image/jpeg", "image/png", "image/webp""image/png" Output image format. A "circle" or "rounded-rectangle" frame exported as "image/jpeg" warns and renders solid black outside the shape — JPEG has no alpha channel. Use PNG/WebP for a transparent crop.
qualitynumber0–1 (jpeg/webp only)browser default Compression quality — lower means smaller file size, more visible artifacts. Ignored for PNG (lossless).
widthnumberany positive pixel sizeframe width Output pixel width. Set this higher than the frame's own width to export at a larger resolution than what's shown on screen (e.g. export a 800px avatar from a 200px on-screen frame).
heightnumberany positive pixel sizeframe height Output pixel height. Same idea as width.
fieldName (upload only)stringany"file" The FormData field name the blob is attached under — match whatever your server endpoint expects (e.g. "avatar").
fileName (upload only)stringany"crop.<ext>", from format The filename the server sees for the uploaded file.
extraFields (upload only)objectany Record<string,string>{} Extra plain-string fields to send alongside the file in the same request — e.g. { userId: "42" }.
fetchOptions (upload only)objectany valid fetch init{} Merged into the underlying fetch() call — use it for headers like Authorization, or credentials: "include" for cookies. method/body are always overridden by upload() itself.
uploader (upload only)functionsame shape as the constructor optionthe constructor's uploader, or the built-in one Overrides the upload implementation for this one call only.
frame.shape: "circle" or "rounded-rectangle" + format: "image/jpeg" logs a console.warn — JPEG has no alpha channel, so the area outside the shape renders solid black instead of transparent. Use "image/png" or "image/webp" for a crop with a transparent background.
Next: Filters.