laurent22/joplin · error · JoplinError

isReadOnly

isReadOnly

Error message

error.message

What it means

Re-thrown by the Joplin Server driver when a PUT to .../content fails because the server reports the target share or folder is read-only (error.code === 'isReadOnly'). Wrapped in a JoplinError with code 'isReadOnly' so callers can distinguish permission denial from other failures.

Source

Thrown at packages/lib/file-api-driver-joplinServer.ts:208

	}

	private isReadyOnlyError(error: { code?: string }) {
		return error && error.code === 'isReadOnly';
	}

	public async put(path: string, content: string | Buffer | null, options: ExecOptions & { shareId?: string } = null) {
		try {
			const output = await this.api().exec('PUT', `${this.apiFilePath_(path)}/content`, options && options.shareId ? { share_id: options.shareId } : null, content, {
				'Content-Type': 'application/octet-stream',
			}, options);
			return output;
		} catch (error) {
			if (this.isRejectedBySyncTargetError(error)) {
				throw new JoplinError(error.message, 'rejectedByTarget');
			}

			if (this.isReadyOnlyError(error)) {
				throw new JoplinError(error.message, 'isReadOnly');
			}

			throw error;
		}
	}

	public async multiPut(items: MultiPutItem[], options: ExecOptions = null) {
		const output = await this.api().exec('PUT', 'api/batch_items', null, { items: items }, null, options);

		for (const [, response] of Object.entries<{ error?: { code?: string | number; httpCode?: number } }>(output.items)) {
			if (response.error && this.isRejectedBySyncTargetError(response.error)) {
				response.error.code = 'rejectedByTarget';
			} else if (response.error && this.isReadyOnlyError(response.error as { code?: string })) {
				response.error.code = 'isReadOnly';
			}
		}

		return output;

View on GitHub (pinned to 2654b33620)

Solutions

  1. Verify the user's permission on the target share/notebook on Joplin Server and request write access if needed.
  2. Move the item out of the read-only share into a writable notebook before editing.
  3. Re-authenticate / re-link the Joplin Server account if permissions changed.
  4. Surface a user-facing message explaining the notebook is read-only rather than retrying blindly.

Example fix

// before
await fileApi.put(path, content, { shareId });
// after
try { await fileApi.put(path, content, { shareId }); }
catch (e) {
  if (e.code === 'isReadOnly') {
    throw new Error('This notebook is read-only; move the note to a writable notebook first.');
  }
  throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

// If you track share permissions client-side, check before PUT.
if (shareId && !shares[shareId]?.canWrite) {
  throw new Error('Target share is read-only');
}

Type guard

function isReadOnlyError(e: any): e is { code: 'isReadOnly' } {
  return e && e.code === 'isReadOnly';
}

Try / catch

try {
  await fileApi.put(path, content, { shareId });
} catch (e) {
  if (isReadOnlyError(e)) {
    // move item to a writable notebook, or show a user-facing notice
    return { skipped: true, reason: 'read-only share' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Uploading content to a shared notebook on Joplin Server where the user has read-only permission, or to a share whose permissions were downgraded after it was added locally.

Common situations: Share recipient with view-only rights tries to edit/sync; share permission changed server-side; a note was moved into a read-only shared folder; user logged into a different account that lacks write access.

Related errors


AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12). Data as JSON: /api/errors/e2010d00ac2e9f00. Report an issue: GitHub.