Change dataset field Status from Published to Draft

I’m having trouble with
Code to change the “Status” field in my CMS dataset from Published to Draft.

Working in
Dev mode

Site link
See screenshot below.

What I’m trying to do
Set the Status record flag from Published to Draft 14 days after the Created Date and the Perpetual value is false. I’m trying to unpublished old records in the dataset as a way to retain the data but not have them appear on my site. Some of the records stay published by excluding them with the Perpetual boolean field.

This code is currently connected to a button but I will move it to an automations schedule or the “back end” once I figure out how to accomplish that.

What I’ve tried so far

This code was generated with velo AI and is crashes saying the classifieds collection does not exist.

import wixData from 'wix-data';

const COLLECTION_NAME = 'Classifieds';
const PAGE_SIZE = 1000;
const AGE_IN_DAYS = 14;

$w.onReady(async function () {
  try {
    const updatedCount = await updateExpiredClassifieds();
    console.log(`Updated ${updatedCount} classified record(s) to draft.`);
  } catch (error) {
    console.error('Failed to update expired classifieds:', error);
  }
});

async function updateExpiredClassifieds() {
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - AGE_IN_DAYS);

  let queryResult = await wixData.query(COLLECTION_NAME)
    .lt('_createdDate', cutoffDate)
    .eq('perpetual', false)
    .limit(PAGE_SIZE)
    .find();

  let updatedCount = 0;

  while (queryResult.items.length > 0) {
    for (const item of queryResult.items) {
      if (item.status !== 'draft') {
        await wixData.update(COLLECTION_NAME, {
          ...item,
          status: 'draft'
        });
        updatedCount += 1;
      }
    }

    if (!queryResult.hasNext()) {
      break;
    }

    queryResult = await queryResult.next();
  }

  return updatedCount;
}```

**Extra context**
Anything else that might help - edge cases, screenshots, etc.

Screenshot of the CMS dataset named Classifieds showing the Status system field and the Perpetual boolean field.

That error is WDE0025, and it means what it says — there’s no collection with that ID on the site.

Collections are referenced by collection ID, not display name. “Classifieds” in your CMS list is the label. The ID may be lowercase, or carry a prefix. Open CMS → your collection → the settings panel and copy the ID exactly. It’s case-sensitive.

Same trap on the fields. “Status” and “Perpetual” are labels; the IDs are likely status and perpetual. Check those in the collection’s field settings before assuming.

Once the IDs are right, this is a backend job with suppressAuth — collection permissions will block the write otherwise:

import wixData from ‘wix-data’;

export async function unpublishOld() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 14);

const results = await wixData.query(‘YOUR_COLLECTION_ID’)
.lt(‘_createdDate’, cutoff)
.eq(‘perpetual’, false)
.eq(‘status’, ‘published’)
.find({ suppressAuth: true });

if (results.items.length) {
const updated = results.items.map(i => ({ …i, status: ‘draft’ }));
await wixData.bulkUpdate(‘YOUR_COLLECTION_ID’, updated, { suppressAuth: true });
}
return results.items.length;
}

Swap the collection ID and confirm the field IDs against your own collection — I can’t see them.

To run it on a schedule, call it from a scheduled job. Note the floor: most plans allow a minimum one-hour interval, and job times are UTC. For a 14-day rule that’s irrelevant, but worth knowing before you write the cron.

It is all looking good I mis-spelled the “Classifieds” collection, so I renamed the label after the fact. However I’am still not achieving the change in the system Status field. The code runs but changes nothing. Any ideas?

import wixData from 'wix-data';

const COLLECTION_ID = 'Classifides'

export async function unpublishOld() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 14);

const results = await wixData.query('COLLECTION_ID')
.lt("_createdDate", cutoff)
.eq("perpetual", false)
.eq("status", "published")
.find({ suppressAuth: true });

if (results.items.length) {
const updated = results.items.map(i => ({ …i, status: `draft` }));
await wixData.bulkUpdate(COLLECTION_ID, updated, { suppressAuth: true });
}
return results.items.length;
}

Status isn’t a field, so bulkUpdate can’t write it. When drafts are enabled the
published and draft copies live in two separate collections — your published
collection, and a shadow collection called Classifides__drafts. Moving an item
between them is a lifecycle operation, not a field update, so there is nothing
bulkUpdate can do here. That’s why it runs clean and changes nothing.

There are two separate reasons you’re seeing zero effect:

.eq(“status”, “published”) filters on a field that doesn’t exist in the data,
so your query almost certainly returns 0 items and the loop never runs. Drop
that line entirely — the published collection only ever contains published
items, so the filter is redundant as well as wrong.

.eq(“perpetual”, false) only matches items where the boolean is explicitly
false. Any row where you never touched the toggle is null, not false, and gets
skipped. Use .ne(“perpetual”, true) so unset rows are included.

The method you actually want is unpublishDataItem from @wix/data. Pass the
PUBLISHED collection ID; it removes the live copy and creates a draft seeded
with the old data. There is no bulk version, so loop.

import { items } from ‘@wix/data’;
import { auth } from ‘@wix/essentials’;

const COLLECTION_ID = ‘Classifides’;

export async function unpublishOld() {
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - 14);

const results = await items
.query(COLLECTION_ID)
.lt(‘_createdDate’, cutoff)
.ne(‘perpetual’, true)
.find();

console.log(‘matched’, results.items.length);

const elevatedUnpublish = auth.elevate(items.unpublishDataItem);

let count = 0;
for (const item of results.items) {
await elevatedUnpublish(COLLECTION_ID, item._id);
count++;
}
return count;
}

Log the match count before the loop. That tells you in one run whether you have
a query problem or a write problem, instead of guessing.

Three things that will bite you next:

The SDK has no suppressAuth. It’s elevate() from @wix/essentials instead, and
only the unpublish needs it — your permissions already let the query read.

find() returns one page. For a retention job that will eventually matter, so
set .limit() high enough or page through the results, or you’ll quietly retire
only the first batch each run.

After unpublishing, the item is in Classifides__drafts, not gone. That’s where
you edit or republish it from — publishDataItemDraft puts it back live.

Your collection ID is fine, by the way. Renaming the label to “Classifieds”
left the ID as “Classifides”, and your code uses the ID, which is correct.
Just make sure the query and the update both use COLLECTION_ID and not the
display name.

Okay. Looks like I’ll have to rethink this. Maybe have another field in the dataset that distinguishes if it is expired. Then add a filter to the repeater to not display the expired records. Your thoughts?

That’s the right call, and it’s a better design than what you started with —
not a retreat.

Publish state is an editorial lifecycle. It’s built for “this article isn’t
finished yet.” Expiry is a fact about the listing. Trying to store one in the
other is why the first approach fought you at every step.

One change to your plan though: make it a date, not a boolean.

A boolean needs something to flip it, and that something is a scheduled job.
Most plans cap jobs at hourly, so there’s always a window where an expired
listing is still live. Worse, if the job fails you don’t find out — the site
just quietly keeps showing stale ads. You’ve added a moving part that can
break silently.

A date has nothing to flip. The listing expires because time passed. Nothing
runs, nothing fails.

Add a Date and Time field, expiresOn, and set it when the listing is created:
created date plus 14 days. Then filter at read time:

import wixData from ‘wix-data’;

$w.onReady(() => {
$w(‘#classifiedsDataset’).setFilter(
wixData.filter()
.eq(‘perpetual’, true)
.or(wixData.filter().gt(‘expiresOn’, new Date()))
);
});

Perpetual listings always show. Everything else shows while expiresOn is still
in the future. No job anywhere.

Do this in code rather than the dataset’s filter panel — the panel wants a
fixed value, and “later than right now” isn’t a fixed value.

The trap to avoid: if you add expiresOn now, every existing row has it empty,
and gt() won’t match an empty field. Your whole catalogue disappears the moment
you publish the filter. Backfill first — one pass setting expiresOn to each
item’s _createdDate plus 14 days — then add the filter.

Two things you get for free that the boolean wouldn’t have given you: you can
show a seller “expires in 3 days”, and renewing a listing is just pushing the
date out. With a flag you’d need a job to un-flip it and no way to know how
long is left.