Upload assets using Prismic Asset API, I can't retain the asset id

I am following: Asset API Technical Reference - Documentation - Prismic

const uploadFile = async (filePath: fs.PathLike, token: string) => {
    const formData = new FormData();
    formData.append('file', fs.createReadStream(filePath));

    const response = await axios.post(assetUrl, formData, {
        headers: {
            Authorization: `Bearer ${token}`,
            'x-api-key': apiKey,
            'Content-Type': 'multipart/form-data',
            'repository': instanceRepository,
            'Accept': "application/json"
        },
    });

    await delay(2000);
    return response
};

I need to migrate assets so that the asset ids match when I use the migration API, however, they are always changed when I use the Prismic Asset API. I cannot seem to force it to have the same ID. It makes sense but I am not sure how to deal with it.

const uploadFile = async (filePath, instanceRepository, assetId, assetUrl) => {

    const formData = new FormData();
    formData.append('file', await fileFromPath(filePath));
    formData.append('assetId', assetId);

    formData.append('id', assetId);
    formData.append('url', assetUrl);
    console.log(formData, 'formData');
    const ResponseUrl = new URL('https://asset-api.prismic.io/assets');
    const authResponse = await fetch('https://auth.prismic.io/login', {
        headers: {
            'Content-Type': 'application/json',
        },
        method: 'POST',
        body: JSON.stringify({
            email,
            password,
        }),
    });
    const token = await authResponse.text();

    const response = await axios.post(ResponseUrl, formData, {
        headers: {
            Authorization: `Bearer ${token}`,
            'x-api-key': destApiKey,
            'Content-Type': 'multipart/form-data',
            'repository': instanceRepository,
            'Accept': "application/json"
        },
    });

    if (!response.status === 200) {
        return console.log('Error:', response.statusText);
    }
    await delay(200);
    console.log('Initial assetId:', assetId);
    console.log('Initial response.data.id:', response.data.id);

    response.data.id = assetId;
    response.data.url = assetUrl;

    console.log('Updated response.data.id:', response.data.id);
    console.log('response data:', response.data);

    return response;

};
async function fetchAssets({ assetType = 'image', limit = 100, repository } = {}) {
        const url = new URL('https://asset-api.prismic.io/assets');
        const authResponse = await fetch('https://auth.prismic.io/login', {
            headers: {
                'Content-Type': 'application/json',
            },
            method: 'POST',
            body: JSON.stringify({
                email,
                password,
            }),
        });
        const token = await authResponse.text();
        const params = {
            assetType,
            limit,
        };
        Object.keys(params).forEach(key => {
            if (params[key]) {
                url.searchParams.append(key, params[key]);
            }
        });

        if (!token) {
            console.error('PRISMIC_API_TOKEN is not set');
            return;
        }
        const response = await fetch(url, {
            headers: {
                Authorization: `Bearer ${token}`,
                'Content-Type': 'application/json',
                'repository': repository,
                'x-api-key': apiKey,
            },
            method: 'GET'
        });
        if (!response.ok) {
            const responseBody = await response.text();
            try {
                const formattedResponse = JSON.parse(responseBody);
                console.error('Response body (JSON):', JSON.stringify(formattedResponse, null, 2));
            } catch (e) {
                console.error('Response body (String):', responseBody);
            }
            console.error('Error:', response.statusText);
            console.log('Doc uid stopped at:', response);
            console.log('===========================================');
            return;
        }
        const data = await response.json();
        const assets = Object.entries(data.items);
        let uploadCount = 0;
        for (const [key, asset] of assets) {
            const { url, id, filename } = asset;
            if (!url) {
                console.error('Invalid URL:', url);
                continue;
            }
            const assetResponse = await fetch(url);
            const filePath = path.join(__dirname, asset.filename);
            const buffer = await assetResponse.arrayBuffer();
            fs.writeFileSync(filePath, Buffer.from(buffer));
            migratedAssetIds.push({ uploadCount, id, filename, url });
            try {
                await uploadFile(filePath, process.env.TARGET_API_KEY, destRepository, id, url);
                console.log('Uploaded asset:', asset.filename, asset.id, "to", destRepository);
                uploadCount++;
                console.log('number uploaded', uploadCount)
            } catch (e) {
                console.log(e);

            } finally {
                console.log('Deleting/unlinking file:', filePath);
                fs.unlinkSync(filePath);
            }
        }
    
    fs.writeFileSync('migrated_asset_ids.json', JSON.stringify(migratedAssetIds, null, 2));
    console.log('Migrated asset ids:', migratedAssetIds, 'number of assets:', migratedAssetIds.length);
    return
}

Any help? :frowning:

Hi James,

Unfortunately, the Prismic Asset API does not support setting a custom asset ID during the upload process. The asset ID is generated by the API upon successful upload like you've seen.

What's your exact use case? Maybe we can find a solution.

Thanks.

Thanks Phil. I have been working on this for a month now and it is my last few days.

I am trying to migrate content to another repo so I can test using the upgrade tool. I have migrated the assets but when I try to migrate the documents this happens:

Working on: sample-page of type: custom-type-sample
404 response status
Response body (String): Assets with following ids not found: XQ_ZIRIAACMX6KQV,XO5ccxAAANqg8Bhd,XSVeCBAAACUA7VTE,ZIGbHxAAACIAogA5,YrHFmREAACIACM4x,YrHCSBEAACMACL4M,YrHJpREAACAACOMM,Zp-9sB5LeNNTxbZn,YrG4yREAACIACJEG,YrHOHxEAACIACPkj,YrG5hREAACMACJQ0,YrHIcBEAACMACNy7,ZIGbTxAAACMAogEO,XO5ccxAAAPhe8Bhc,ZIGbNhAAACAAogCh,XO5cixAAANqg8BjY,YrGdABEAACAACCXL,YrG4ThEAACMACI7p,ZIHNnxAAACMAowAx,ZO8zdRQAACgAhMmF,YrGcJxEAACIACCRA,Zr9x6kaF0TcGJASQ,Zp-7lx5LeNNTxbYV,Zp-7Zx5LeNNTxbYB,ZNuJrxAAACAAyarO,ZPdK7hQAACcAqM_E,ZrXj60aF0TcGI0NU,Zp-7PR5LeNNTxbX7

This happens on all pages because the id's don't match. This is my full script:

import 'dotenv/config';
import { createClient } from '@prismicio/client';
import fetch from 'node-fetch';

const chunkArray = (array, size) => {
    return Array.from({ length: Math.ceil(array.length / size) }, (v, i) =>
        array.slice(i * size, i * size + size)
    );
};


async function migrateDocument(doc) {
    const email = process.env.EMAIL;
    const password = process.env.PASSWORD;
    const repository = process.env.REPOSITORY_DEST;
    const apiKey = process.env.API_KEY;
    const url = `https://migration.prismic.io/documents/`;

    const authResponse = await fetch('https://auth.prismic.io/login', {
        headers: {
            'Content-Type': 'application/json',
        },
        method: 'POST',
        body: JSON.stringify({
            email,
            password,
        }),
    });
    const token = await authResponse.text();

    const response = await fetch(url, {
        headers: {
            Authorization: `Bearer ${token}`,
            'x-api-key': apiKey,
            'Content-Type': 'application/json',
            repository,
        },
        method: 'POST',
        body: JSON.stringify(doc),
    });
    console.log('Working on:', doc.uid, 'of type:', doc.type);
    console.log(response.status, 'response status');
    if (!response.ok) {
        const responseBody = await response.text();
        try {
            const formattedResponse = JSON.parse(responseBody);
            console.error('Response body (JSON):', JSON.stringify(formattedResponse, null, 2));
        } catch (e) {
            console.error('Response body (String):', responseBody);
            return;
        }
        console.error('Error:', response.statusText);
        console.log('Doc uid stopped at:', doc.uid, 'of type:', doc.type);
        console.log('===========================================');

    }
    const data = await response.json();
    console.log(data, 'data');
}


async function init(lang) {
    const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
    const client = createClient(process.env.REPOSITORY_SOURCE);
    const response = await client.dangerouslyGetAll({ lang: lang });

    const chunks = chunkArray(response, 100);

    await Promise.all(chunks.map(async (chunk) => {
        const processedChunk = chunk.map(doc => {
            if (!doc.title) {
                return { ...doc, title: doc.uid };
            }
            return doc;
        });

        for (const doc of processedChunk) {
            try {
                await migrateDocument(doc);
                await delay(1000);
                if (doc === processedChunk.length - 1) {
                    const now = new Date();
                    console.log('Document migration finished at,', now);
                }
            } catch (error) {
                if (error.message.includes('Assets with following ids not found')) {
                    console.error('Error:', error.message);
                    console.log('Doc uid stopped at:', doc.uid, 'of type:', doc.type);
                    console.log('===========================================');
                }
            }
        }
    }));
}

init("es-es");

So I ended up replacing the asset id and URL for every image that it couldn't find with an image that was already on the new repo. It probably wouldn't be satisfactory if I wasn't just testing but it will do for now.

Hey James,

I'm glad you got it working. If it's still helpful I think these example scripts help do the assets matching to the new repositories to help you put the images back in the correct documents:

Let me know if this helps.

Thanks, I will add this for the other site migration later.