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
- Verify the user's permission on the target share/notebook on Joplin Server and request write access if needed.
- Move the item out of the read-only share into a writable notebook before editing.
- Re-authenticate / re-link the Joplin Server account if permissions changed.
- 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
- Cache and refresh share permission flags client-side.
- Disable editing UI for items in read-only shares.
- Re-link the server account when permissions change server-side.
- Avoid moving items into read-only shares programmatically.
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
- Could not access data on server "${options.path()}"
- Cannot convert read-only item: "%s"
- Could not verify the share status of this notebook - abortin
- rejectedByTarget
- rejectedByTarget
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/e2010d00ac2e9f00.
Report an issue: GitHub.